﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using Noris.Schedule.Support.Core;
using Noris.Schedule.Support.Sql;

namespace Noris.Schedule.Support.Green
{
	#region GENERÁTOR QUERY NA ZÁKLADĚ STROMU TŘÍD GREEN
	/// <summary>
	/// Třída, která interně řeší logiku stromu tříd ve filtrech
	/// </summary>
	internal class NorisClassTree : IDisposable
	{
		#region KONSTRUKCE
		private NorisClassTree()
		{
			this._UsedTableList = new List<QueryUsedTables>();
		}
		/// <summary>
		/// Statický konstruktor
		/// </summary>
		/// <param name="classData">Data o master třídě</param>
		/// <param name="entites">Seznam entit</param>
		/// <param name="prefixAlias">Prefix aliasu, bude se používat pro aliasy tabulek použitých pro reálné joiny</param>
		/// <param name="phraseFrom">Odkaz na existující frázi FROM, o kterou se strom tříd může opírat.
		/// Pokud je null, pak strom tříd staví select od master třídy sám.
		/// Pokud je předána existující fráze, pak strom tříd se pokouší najít tabulky své master třídy a joinovat se na ně, na jejich aliasy.
		/// </param>
		/// <param name="prefixError">Text chyby, když je nalezena jedna entita v daném seznamu vícekrát. 
		/// Text má znít například: "Ve filtru %0 je chyba".
		/// Bude doplněn text: ": entita %1 se vyskytuje vícekrát." </param>
		internal static NorisClassTree Create(ClassInfo classData, IEnumerable<INorisClassTreeEntity> entites, string prefixAlias, SqlParsePhraseFrom phraseFrom, string prefixError)
		{
			NorisClassTree classTree = new NorisClassTree();
			classTree._PhraseFrom = phraseFrom;
			classTree._MasterClassData = classData;
			classTree._PrefixAlias = (!String.IsNullOrEmpty(prefixAlias) ? prefixAlias.Trim() : "A" + (new Random().Next(100, 1000).ToString()));
			classTree._PrefixError = prefixError;
			classTree._EntityInit(entites);
			classTree._RootClasFromInit();
			return classTree;
		}
		void IDisposable.Dispose()
		{
			this._UsedTableList = null;
			this._PhraseFrom = null;
			this._MasterClassData = null;
			this._EntityDict = null;
		}
		/// <summary>
		/// Připraví indexovaný soupis entit
		/// </summary>
		/// <param name="entites"></param>
		private void _EntityInit(IEnumerable<INorisClassTreeEntity> entites)
		{
			this._EntityDict = new Dictionary<int, EntityTableGroup>();

			foreach (INorisClassTreeEntity entity in entites)
			{
				if (this._EntityDict.ContainsKey(entity.EntityNumber))
					Throw.SysError(_PrefixAlias + MessageInfo.Get("entita %1 se vyskytuje vícekrát.", this.ToString(), entity.EntityNumber));
				else
					this._EntityDict.Add(entity.EntityNumber, EntityTableGroup.CreateForEntity(entity));
			}
			if (!this._EntityDict.ContainsKey(1))
				this._EntityDict.Add(1, EntityTableGroup.CreateForClass(this._MasterClassData));
		}
		/// <summary>
		/// Připraví informace o tom, které tabulky Root třídy jsou použity ve frázi FROM, o kterou se tento strom opírá.
		/// Tzn. projde všechny tabulky Root entity, a každou se pokusí najít ve frázi FROM.
		/// Pokud ji najde, pak si do entity do konkrétní tabulky poznamená, že je k dispozici ve frázi FROM a její zjištěný alias.
		/// </summary>
		private void _RootClasFromInit()
		{
			if (this._PhraseFrom == null) return;         // Není známa fráze FROM.

			EntityTableGroup entityRoot;
			if (!this._EntityDict.TryGetValue(1, out entityRoot))
				return;                                   // Není obsažena master entita.

			List<EntityTableOne> tableList = entityRoot.GetAllTables();         // Všechny tabulky z Root entity
			List<SqlParseFromTable> fromList = this._PhraseFrom.FromTableList;  // Všechny tabulky z FROM fráze, včetně dalších dat
			List<EntityTableOne> findList = new List<EntityTableOne>();

			// Najdu první tabulku ve FROM, která má jméno odpovídající Root entitě 
			// a jde o Master nebo Entries tabulku (subjekty v první fázi nehledám, ty jsou zavádějící):
			EntityTableOne findTable = null;
			string pairAlias;
			foreach (SqlParseFromTable from in fromList)
			{	// Projdu všechny položky FROM z daného SELECTU:
				EntityTableOne table = tableList.Find(t =>  
					(t.TableType == ClassTableType.Master || t.TableType == ClassTableType.Entries) && String.Equals(t.TableName, from.Source, StringComparison.OrdinalIgnoreCase));
				if (table == null) continue;

				// Pokud je tato tabulka ve FROM navázaná systémově, pak je základem pro vázání dalších tabulek:
				if (_RootFromJoinIsSystem(from, entityRoot, table, out pairAlias))
				{
					findTable = table;
					findTable.Alias = from.AliasSource;
					findTable.PairAlias = pairAlias;
					findTable.IsInFrom = true;
					break;
				}
			}

			// Pokud ve frázi FROM žádná root tabulka není, je to chyba:
			if (findTable == null)
				Throw.SysError(MessageInfo.Get("Chyba: daný SELECT neobsahuje žádnou tabulku třídy, nad kterou je připravován strom tříd."));

			// Poté budu hledat zbývající tabulky root entity, pokud jsou systémově navázané ke známým tabulkám:
			findList.Add(findTable);
			tableList.Remove(findTable);              // Ze vstupní tabulky odeberu tabulku, kterou již známe.
			while (tableList.Count > 0)
			{	// Dokud není všechno nalezeno (anebo smyčku breaknu když nenajdu nic):
				// Najdu některou tabulku ve frázi FROM, která je mezi těmi, které jsme ještě nenašli (seznam tableList),
				// a která je systémově navázaná na některou z těch tabulek, které jsme už našli:
				findTable = null;
				foreach (EntityTableOne table in tableList)
				{	// Pro každou dosud nevyřešenou tabulku z naší entity:
					// Najdu položky FROM, které do Query napojují tabulky tohoto jména:
					List<SqlParseFromTable> eqList = fromList.FindAll(f => String.Equals(table.TableName, f.Source, StringComparison.OrdinalIgnoreCase));
					if (eqList.Count == 0) continue;    // Tuhle tabulku v Query nemáme.

					// Hledanou tabulku (pro naši root entitu) jsme v Query našli, možná vícekrát.
					// Pokud se podaří potvrdit alespoň jeden výskyt (odpovídající řádek z Query),
					//  který má systémový JOIN na některou z již potvrzených tabulek,
					//  pak si můžeme potvrdit další nalezenou tabulku:
					foreach (SqlParseFromTable eqFrom in eqList)
					{	// Pro každý řádek FROM z Query, kde je tabulka "table":
						if (_RootFromJoinIsSystem(eqFrom, entityRoot, table, out pairAlias, findList))
						{	// Tento řádek FROM obsahuje systémově joinovanou tabulku, kterou hledáme, 
							// s joinem na některou již potvrzenou tabulku:
							findTable = table;
							findTable.Alias = eqFrom.AliasSource;
							findTable.PairAlias = pairAlias;
							findTable.IsInFrom = true;
							break;
						}
					}
					if (findTable != null)
						// Něco jsme našli => další tabulku nehledáme, ale tu nalezenou jdeme zpracovat:
						break;
				}
				if (findTable != null)
				{	// Našli jsme tabulku: zařadíme ji do nalezených, odebereme z hledaných, a jdeme dál:
					findList.Add(findTable);
					tableList.Remove(findTable);
					continue;
				}
				else
					// Nic jsme nenašli => skončíme.
					break;
			}
		}
		/// <summary>
		/// Zjistí, zda výskyt tabulky ve frázi FROM je možno považovat za příslušný k Root entitě.
		/// Tabulka je odpovídající, prověříme JOIN.
		/// Vrací true, pokud je tabulka zapojená slovem FROM, anebo je joinovaná na jinou tabulku oboustranně přes identity column tabulky své nebo master.
		/// </summary>
		/// <param name="from">Řádek z fráze FROM (jedna reálně použitá tabulka)</param>
		/// <param name="entityRoot">Parent entita = nese data třídy</param>
		/// <param name="table">Konkrétní tabulka</param>
		/// <param name="pairAlias">Out: alias tabulky na druhé straně joinu</param>
		/// <returns></returns>
		private static bool _RootFromJoinIsSystem(SqlParseFromTable from, EntityTableGroup entityRoot, EntityTableOne table, out string pairAlias)
		{
			pairAlias = null;
			if (from.JoinType == JoinTableType.From)
				// Tato položka je skutečně SELECT ... FROM tabulka => je to ROOT tabulka, bez pochyby.
				return true;

			// Z fráze ON si najdu výrazy okolo prvního rovnítka (čili jakým výrazem je JOIN ON navázán):
			string joinA, joinB;
			_RootFromJoinParse(from.OnExpressions, out joinA, out joinB);
			if (String.IsNullOrEmpty(joinA) || String.IsNullOrEmpty(joinB))
				return false;                        // Nelze rozpoznat dvě strany v rovnosti JOIN ON (a = b).

			// Tuto tabulku mohu považovat za Root tehdy, když je joinována přes svůj identity klíč na tentýž identity klíč jiné tabulky:
			// Takže pokud najdu join dle výrazu: "po.cislo_subjektu" = "sub.cislo_subjektu", pak je to systémový join.
			// Pokud ale najdu join "po.cislo_subjektu" = "sub.product_order", pak to není systémový join.
			string keyDbCol = table.KeyColumn;       // Název DB sloupce, který je Identity v dané tabulce
			bool isSystemJoin = (joinA.EndsWith(keyDbCol) && joinB.EndsWith(keyDbCol));
			if (!isSystemJoin)
			{	// Pokud jde o tabulky položek, pak se ještě podívám, zda není navázaná přes číslo subjektu = to je taky systémová vazba:
				if (table.TableType == ClassTableType.Entries || table.TableType == ClassTableType.UdaEntries)
				{
					keyDbCol = entityRoot.KeyColumn;     // Název DB sloupce, který je Identity v master tabulce (cislo_subjektu)
					isSystemJoin = (joinA.EndsWith(keyDbCol) && joinB.EndsWith(keyDbCol));
				}
			}

			// Pokud jsme nalezli systémový JOIN, pak máme zjistit alias druhé strany:
			if (isSystemJoin)
				pairAlias = _RootFindPairAlias(joinA, joinB, from.AliasSource);

			return isSystemJoin;
		}
		/// <summary>
		/// Zjistí, zda výskyt tabulky ve frázi FROM je možno považovat za příslušný k Root entitě.
		/// Tabulka je odpovídající, prověříme JOIN.
		/// Vrací true, pokud je tabulka zapojená slovem FROM, anebo je joinovaná na jinou tabulku oboustranně přes identity column tabulky své nebo master.
		/// </summary>
		/// <param name="from">Řádek z fráze FROM (jedna reálně použitá tabulka)</param>
		/// <param name="entityRoot">Parent entita = nese data třídy</param>
		/// <param name="table">Konkrétní tabulka</param>
		/// <param name="pairAlias">Out: alias tabulky na druhé straně joinu</param>
		/// <param name="findList">Seznam tabulek, které patří k téže entitě a již jsme je v SELECT FROM spolehlivě detekovali.
		/// Pokud je volána tato varianta, výrazně se mění vnitřní chování funkce (přestože vnější chování je shodné s druhým přetížením).</param>
		/// <returns></returns>
		private static bool _RootFromJoinIsSystem(SqlParseFromTable from, EntityTableGroup entityRoot, EntityTableOne table, out string pairAlias, List<EntityTableOne> findList)
		{
			pairAlias = null;
			if (from.JoinType == JoinTableType.From)
			{	// Tato položka je skutečně SELECT ... FROM tabulka => 
				// Akceptujeme ji jako Root tabulku tehdy, když se na tuto (FROM) tabulku systémově váže některá z již potvrzených tabulek:
				EntityTableOne find = findList.Find(t => String.Equals(from.AliasSource, t.PairAlias, StringComparison.OrdinalIgnoreCase));
				if (find != null)
				{	// Skutečně, našli jsme některou již známou tabulku, která se joinuje na tabulku, která je zde FROM:
					pairAlias = find.Alias;          // Jde o párový alias, který si zapamatujeme k tabulce SELECT FROM.
					return true;
				}
				// Je to sice tabulka FROM, ale nikdo z našich známých se na ni neváže, zatím ji nepotvrdím jako "naši" tabulku:
				return false;
			}

			// Z fráze ON si najdu výrazy okolo prvního rovnítka (čili jakým výrazem je JOIN ON navázán):
			string joinA, joinB;
			_RootFromJoinParse(from.OnExpressions, out joinA, out joinB);
			if (String.IsNullOrEmpty(joinA) || String.IsNullOrEmpty(joinB))
				return false;                        // Nelze rozpoznat dvě strany v rovnosti JOIN ON (a = b).

			// Tuto tabulku mohu považovat za součást tabulek Root svazku tehdy, 
			// když je joinována přes svůj identity klíč na tentýž identity klíč jiné tabulky,
			// přičemž tato jiná tabulka už musí být uvedena (prostřednictvím aliasu) v seznamu mých známých tabulek.
			string keyDbCol = table.KeyColumn;       // Název DB sloupce, který je Identity v dané tabulce
			bool isSystemJoin = (joinA.EndsWith(keyDbCol) && joinB.EndsWith(keyDbCol));
			if (!isSystemJoin)
			{	// Pokud jde o tabulky položek, pak se ještě podívám, zda není navázaná přes číslo subjektu = to je taky systémová vazba:
				if (table.TableType == ClassTableType.Entries || table.TableType == ClassTableType.UdaEntries)
				{
					keyDbCol = entityRoot.KeyColumn;     // Název DB sloupce, který je Identity v master tabulce (cislo_subjektu)
					isSystemJoin = (joinA.EndsWith(keyDbCol) && joinB.EndsWith(keyDbCol));
				}
			}

			// Pokud jsme nalezli systémový JOIN, pak tabulku FROM odsouhlasíme jako "naši" tehdy, 
			//  když je její alias (from) uvedeno jako párové alias některé z "našich" tabulek:
			if (isSystemJoin)
			{
				EntityTableOne find = findList.Find(t => String.Equals(from.AliasSource, t.PairAlias, StringComparison.OrdinalIgnoreCase));
				if (find != null)
				{	// Tuto FROM tabulku někdo z "našich" zná!
					pairAlias = _RootFindPairAlias(joinA, joinB, from.AliasSource);
					// Nyní by se mělo pairAlias shodovat s domácím aliasem tabulky find:
					isSystemJoin = String.Equals(pairAlias, find.Alias, StringComparison.OrdinalIgnoreCase);
				}
			}

			return isSystemJoin;
		}
		/// <summary>
		/// Najdi a vrať alias opačné strany JOINu.
		/// </summary>
		/// <param name="joinA">JOIN ON výraz na jedné straně rovnítka</param>
		/// <param name="joinB">JOIN ON výraz na druhé straně rovnítka</param>
		/// <param name="alias">Alias na jedné straně, hledáme alias na opačné straně.</param>
		/// <returns></returns>
		private static string _RootFindPairAlias(string joinA, string joinB, string alias)
		{
			if (String.Equals(_RootFindAlias(joinA, alias), alias, StringComparison.OrdinalIgnoreCase))
				return _RootFindAlias(joinB);
			if (String.Equals(_RootFindAlias(joinB, alias), alias, StringComparison.OrdinalIgnoreCase))
				return _RootFindAlias(joinA);
			return null;
		}
		/// <summary>
		/// Vrátí alias z daného výrazu.
		/// Z výrazu "database.lcs.product_order.status" vrátí "database.lcs.product_order".
		/// Z výrazu "lcs.product_order.status" vrátí "lcs.product_order".
		/// Z výrazu "po.status" vrátí "po".
		/// Z výrazu "status" vrátí "".
		/// </summary>
		/// <param name="join">Výraz, definující nějaký sloupec ("lcs.product_order.status", "po.status", "status")</param>
		/// <returns></returns>
		private static string _RootFindAlias(string join)
		{
			if (join == null) return null;
			if (join.Trim().Length == 0) return "";
			int lastDot = join.LastIndexOf('.');
			if (lastDot <= 0) return "";
			return join.Substring(0, lastDot);
		}
		/// <summary>
		/// Vrátí alias z daného výrazu, anebo defaultní alias pokud ve výrazu žádný není.
		/// Z výrazu "lcs.product_order.status" vrátí "lcs.product_order".
		/// Z výrazu "po.status" vrátí "po".
		/// Z výrazu "status" vrátí defAlias.
		/// Z výrazu "" vrátí "" (a ne defAlias).
		/// </summary>
		/// <param name="join">Výraz, definující nějaký sloupec ("lcs.product_order.status", "po.status", "status")</param>
		/// <param name="defAlias">default alias</param>
		/// <returns></returns>
		private static string _RootFindAlias(string join, string defAlias)
		{
			if (join == null) return null;
			if (join.Trim().Length == 0) return "";
			int lastDot = join.LastIndexOf('.');
			if (lastDot <= 0) return defAlias;
			return join.Substring(0, lastDot);
		}
		/// <summary>
		/// V dané frázi (výraz ON v rámci FROM.JOIN) najde a separuje první dvojici výrazů na stranách rovnítka.
		/// Jinými slovy, z fráze ON: "(po.cislo_subjektu = subj.cislo_subjektu and po.stav = 'A')"
		/// najde a vrátí (do out parametrů) výrazy okolo prvního rovnítka: joinA = "po.cislo_subjektu", joinB = "subj.cislo_subjektu".
		/// </summary>
		/// <param name="list"></param>
		/// <param name="joinA"></param>
		/// <param name="joinB"></param>
		private static void _RootFromJoinParse(List<SqlParseItem> list, out string joinA, out string joinB)
		{
			joinA = "";
			joinB = "";
			int eqIdx = list.FindIndex(sqlItem => sqlItem.TextComponent == SqlParseTextComponent.Delimiter && sqlItem.Text == "=");
			if (eqIdx <= 0) return;

			// Najdu první výraz (SqlText) vlevo:
			for (int a = eqIdx - 1; a >= 0; a--)
			{
				if (list[a].TextComponent == SqlParseTextComponent.SqlText)
				{
					joinA = list[a].Text;
					break;
				}
			}
			// Najdu první výraz (SqlText) vpravo:
			for (int b = eqIdx + 1; b < list.Count; b++)
			{
				if (list[b].TextComponent == SqlParseTextComponent.SqlText)
				{
					joinB = list[b].Text;
					break;
				}
			}
		}
		/// <summary>
		/// Data o master třídě
		/// </summary>
		private ClassInfo _MasterClassData;
		/// <summary>
		/// Příznak (true), pokud máme napojenou frázi (SELECT) FROM.
		/// </summary>
		public bool IsPhraseFrom { get { return (this._PhraseFrom != null); } }
		/// <summary>
		/// Fráze FROM příkazu, o který se tento strom může opírat (nebo zde může být null, pak strom roste od vlastního kořene)
		/// </summary>
		private SqlParsePhraseFrom _PhraseFrom;
		/// <summary>
		/// Prefix aliasů, které se použijí pro reálně použité entity
		/// </summary>
		private string _PrefixAlias;
		/// <summary>
		/// Prefix textu chyb, popisuje druh vlastníka a jeho název.
		/// Typický obsah: "Ve filtru %0 je chyba".
		/// </summary>
		private string _PrefixError { get { return __PrefixError; } set { __PrefixError = (String.IsNullOrEmpty(value) ? "" : value.Trim() + ": "); } }
		/// <summary>
		/// Úložiště pro prefix textu chyb
		/// </summary>
		private string __PrefixError;
		/// <summary>
		/// Poslední použité číslo alias
		/// </summary>
		private int _LastAliasNumber;
		/// <summary>
		/// Soupis entit, indexovaný, podle vstupující mapy
		/// </summary>
		private Dictionary<int, EntityTableGroup> _EntityDict;
		/// <summary>
		/// Souhrn tabulek, zařazených do Query
		/// </summary>
		private List<QueryUsedTables> _UsedTableList;
		#endregion
		#region VYHLEDÁNÍ SLOUPCE, URČENÍ JEHO TABULKY, VZTAHŮ NA PARENT ENTITY, SESTAVOVÁNÍ SEKCE FROM V QUERY
		/// <summary>
		/// Najde a vrátí zdroj dat daného sloupce pro daný vstupní údaj.
		/// Zajistí vyhledání entity, zajistí vložení entity do query, včetně všech parent entit.
		/// Převede název tabulky v db columnu na alias v rámci query.
        /// Řeší i položku představující Výraz: vrací zadaný výraz, upravený tak, aby byl součástí aktuálního stromu tříd 
        /// (najde použité tabulky pocházející z třídy entity, tabulky zapojí do stromu tříd a použije jejich alias).
		/// </summary>
		/// <param name="entry">Data o položce (filtru, sestavy).</param>
		/// <returns>Název db sloupce v query</returns>
		internal string GetEntryColumnSource(INorisClassTreeEntry entry)
		{
			// Vyhledám entitu daného čísla:
            EntityTableGroup entity = this.FindEntityTableGroup(entry.EntityNumber);

            // A buď ji řeším jako Výraz, nebo jako DbColumn:
            if (IsExpression(entry.DataColumnSource))
                return this.GetEntrySourceExpression(entry, entity);
            else
                return this.GetEntrySourceDbColumn(entry, entity);
		}
        /// <summary>
        /// Zajistí napojení výrazu do stromu tříd a náhradu názvů tabulek jejich aliasem
        /// </summary>
        /// <param name="entry"></param>
        /// <param name="entity"></param>
        /// <returns></returns>
        private string GetEntrySourceExpression(INorisClassTreeEntry entry, EntityTableGroup entity)
        {
            string expression = entry.DataColumnSource;
            foreach (EntityTableOne table in entity.TableDict.Values)
            {
                string tableName = table.TableName.ToLower() + ".";
                if (expression.Contains(tableName))
                {   // Určím alias pro tuto tabulku, zařadím tabulku do seznamu použitých tabulek ve stromu tříd:
                    // Pokud tabulka není připravená pro použití v query, zajistím to (a rekurzivně ke všem jejím potřebným parentům):
                    this._ConnectTableIntoQuery(entity, table);

                    // Upravím výraz tak, aby odkazy na tuto tabulku byly nahrazeny jejím aliasem:
                    expression = table.RenameExpressionToAlias(expression);
                }
            }
            return expression;
        }
        /// <summary>
        /// Najde a vrátí název DB sloupce pro daný vstupní údaj.
        /// </summary>
        /// <param name="entry"></param>
        /// <param name="entity"></param>
        /// <param name="table"></param>
        /// <returns></returns>
        private string GetEntrySourceDbColumn(INorisClassTreeEntry entry, EntityTableGroup entity)
        {
            // Najdu tabulku, z níž má pocházet hledaný column:
            EntityTableOne table = entity.FindTableWithColumn(entry.DataColumnSource);

            // Vrátím aliasovaný název db sloupce. 
            // Tím rozběhnu rekurzivní zařazování použitých vztažených (nadřazených, nahoru k root entitě) tabulek do joinu v query:
            return this._RenameColumnToAlias(entity, table, entry.DataColumnSource);
        }
        /// <summary>
        /// Vrací příznak, zda daný DB column je výrazem. Určuje to podle obsahu.
        /// Pokud DB column obsahuje slova jako "SELECT", "FROM", "=" a mezery, pak jde o výraz.
        /// </summary>
        /// <param name="fullDbColumn"></param>
        /// <returns></returns>
        internal static bool IsExpression(string fullDbColumn)
        {
            string text = fullDbColumn.ToUpper();
            if (text.Contains("=") || text.Contains(">") || text.Contains("<") || text.Contains("(") || text.Contains(")") || text.Contains("+") || text.Contains("-") || text.Contains("*") || text.Contains("/")) return true;
            text = text.Replace("(", " ").Replace(")", " ").Replace("\r", " ").Replace("\n", " ").Replace("\t", " ");
            if (!text.Contains(" ")) return false;           // Bez mezer (bez závorek a CRLF) nejde napsat výraz.
            text = " " + text + " ";
            return (text.Contains(" SELECT ") && text.Contains(" FROM "));
        }
		/// <summary>
		/// Metoda vrátí seznam tabulek, které jsou aktuálně připraveny k použití
		/// </summary>
		/// <returns></returns>
		internal string GetUsedTableList()
		{
			string tableList = String.Empty;
			string eol = Environment.NewLine;
			foreach (QueryUsedTables table in this._UsedTableList)
				tableList += table.ToQuery() + eol;
			return tableList;
		}
		/// <summary>
		/// Najde data o vztahu na danou entitu, najde entitu parenta
		/// </summary>
		/// <param name="entityCurrent"></param>
		/// <param name="relInfo"></param>
		/// <param name="entityParent"></param>
		/// <returns></returns>
		private bool _FindParentEntity(EntityTableGroup entityCurrent, out RelationInfo relInfo, out EntityTableGroup entityParent)
		{
			// Teoreticky by měla být zdejší entita (tableGroup) navázaná na Parent entitu, ale prakticky to tak být nemusí.
			// Mohu být navázaný na některou ještě vyšší entitu (parent mého parenta). 
			// Který parent to je, to poznám to podle shody třídy na vztahu relInfo a třídy na parent entitě.

			// Na této entitě (tableGroup) je uveden vztah, kterým je tato entita navázaná do stromu tříd směrem k Rootu:
			relInfo = entityCurrent.RelationData;
			if (relInfo == null)
				Throw.SysError(_PrefixAlias + MessageInfo.Get("entita %0 má parent entitu %1, ale nelze najít vztah %2 z parent entity.", entityCurrent.EntityNumber, entityCurrent.ParentEntityNumber, entityCurrent.RelationNumber));

			// Rekurzivně najdu entitu parenta, která je takové třídy, jaké třídy je vztah, který vede na entityCurrent:
			entityParent = null;
			int parent = entityCurrent.ParentEntityNumber;
			RelationSideType relSide = entityCurrent.RelationSide;
			while (parent > 0)
			{
				entityParent = this.FindEntityTableGroup(parent);
				if ((relSide == RelationSideType.Left && entityParent.ClassData.CisloTridy == relInfo.CisloTridy) ||
					(relSide == RelationSideType.Right && entityCurrent.ClassData.CisloTridy == relInfo.CisloTridy) ||
					(relSide == RelationSideType.None))
					break;
				parent = entityParent.ParentEntityNumber;
				if (parent == 0)
					entityParent = null;
			}
			return (entityParent != null);
		}
		/// <summary>
		/// Najde data dané entity, anebo vyhodí chybu
		/// </summary>
		/// <param name="entityNumber"></param>
		/// <returns></returns>
		protected EntityTableGroup FindEntityTableGroup(int entityNumber)
		{
			EntityTableGroup tableGroup;
			if (!this._EntityDict.TryGetValue(entityNumber, out tableGroup))
				Throw.SysError(this._PrefixError + MessageInfo.Get("nebyla nalezena entita číslo %0.", entityNumber));
			return tableGroup;
		}
		#endregion
		#region ZAŘAZOVÁNÍ TABULEK DO QUERY
		/// <summary>
		/// Vrátí název daného sloupce, jméno tabulky je nahrazeno odpovídajícím aliasem.
		/// Tabulka je v případě potřeby touto metodou zařazena do query.
		/// </summary>
		/// <param name="currentEntity"></param>
		/// <param name="currentTable"></param>
		/// <param name="fullDbColumnName"></param>
		/// <returns></returns>
		private string _RenameColumnToAlias(EntityTableGroup currentEntity, EntityTableOne currentTable, string fullDbColumnName)
		{
			// Pokud tabulka není připravená pro použití v query, zajistím to (a rekurzivně ke všem jejím potřebným parentům):
			this._ConnectTableIntoQuery(currentEntity, currentTable);

			// Přejmenuji sloupec fullname na aliasovaný a vrátím jej:
			return currentTable.RenameColumnToAlias(fullDbColumnName);
		}
		/// <summary>
		/// Zapojí danou tabulku do Query.
		/// Pokud je známa fráze FROM, a jde o Root entitu, pokusí se najít tuto tabulku ve frázi FROM.
		/// Pokud se to nepodaří, zařadí tabulku do query jako novou pod jejím zdejším (filtrovým) aliasem.
		/// </summary>
		private void _ConnectTableIntoQuery(EntityTableGroup currentEntity, EntityTableOne currentTable)
		{
			if (currentTable.IsConnected)
				return;

			// Tento blok zde neprovádíme, protože tabulky Root entity hledáme ve FROM frázi hned při Initu.
			if (this._PhraseFrom != null && currentEntity.IsRoot && currentEntity.ClassData.CisloTridy == this._MasterClassData.CisloTridy)
			{	// Máme opěrnou frázi FROM, a daná tabulka je tabulkou master třídy filtru = mohli bychom využít tabulku ze SELECTU:
				//    this._ConnectTableFindInFrom(currentEntity, currentTable);
			}
			if (!currentTable.IsConnected)
				this._AddNewTableIntoQuery(currentEntity, currentTable);
		}
		/// <summary>
		/// Připraví danou tabulku pro použití v Query.
		/// Jde o entitu master třídy, a máme k dispozici frázi FROM.
		/// Tabulku se nejprve pokusíme najít ve frázi FROM (a přečíst si její alias),
		/// anebo (pokud tam nebude) ji do této fráze najoinovat.
		/// Výsledky se mají promítnout do tabulky předané jako parametr currentTable.
		/// </summary>
		/// <param name="currentEntity">Entita = svazek tabulek jedné třídy</param>
		/// <param name="currentTable">Tabulka</param>
		private void _ConnectTableFindInFrom(EntityTableGroup currentEntity, EntityTableOne currentTable)
		{
			// Podívám se, zda požadovaná tabulka už ve frázi FROM existuje:
			string fromAlias = this._PhraseFrom.FindAliasForTable(currentTable.TableName);
			if (!String.IsNullOrEmpty(fromAlias))
			{	// Požadovanou tabulku jsme ve frázi FROM našli, máme její alias, použijeme ji:
				currentTable.Alias = fromAlias;
				currentTable.IsInFrom = true;
				return;
			}

			// Tuto tabulku ve frázi FROM nemáme, měli bychom ji zapojit:
			currentTable.Alias = this._GetTableAlias(currentEntity.EntityNumber, currentTable.TableType);

			// Tabulky jedné entity (tj. třídy) mají v kompletní podobě tuto strukturu:
			//      Subjekt == Master => UDA Master
			//              => Entries => UDA Entries
			//      kde == je JOIN a => je LEFT OUTER JOIN.
			// U entit, které jsou podřízené jiné entitě, v položkovém vztahu zprava, je hierarchie jiná, tam je Rootem tabulka Entries.

			// Zde požadujeme určitou tabulku (currentTable) v rámci této struktury, ale ta zatím není v reálné frázi FROM obsažena.
			// Budeme ji tedy zapojovat, a je třeba ji správně najoinovat na existující strukturu Query.
			string joinClause, joinComment;
			QueryEnlistType joinType;

			// Dva chody, dívám se nejdřív vlevo ve struktuře (od položek k Master a Subject), pak vpravo (naopak):
			for (int side = 0; side < 2; side++)
			{	// Seznam sousedních tabulek: nejdřív vlevo (side == 0), pak (side == 1) vpravo od currentTable:
				List<EntityTableOne> otherTables =
					(side == 0 ?
					currentEntity.GetLeftTables(currentTable) :
					currentEntity.GetRightTables(currentTable));

				foreach (EntityTableOne otherTable in otherTables)
				{
					string otherAlias = this._PhraseFrom.FindAliasForTable(otherTable.TableName);
					if (!String.IsNullOrEmpty(otherAlias))
					{	// Tabulka vlevo nebo vpravo ve frázi FROM je, takže naši tabulku napojíme na ní:
						otherTable.Alias = otherAlias;
						otherTable.IsInFrom = true;
						currentEntity.DetectJoinData(currentTable, otherTable, out joinType, out joinClause, out joinComment);
						if (joinType == QueryEnlistType.LeftJoin || joinType == QueryEnlistType.FullJoin)
						{
							this._JoinTableIntoQuery(currentEntity, currentTable, joinType, joinClause, joinComment);
							return;
						}
					}
				}
			}
		}
		/// <summary>
		/// Zařadí danou tabulku a entitu do seznamu pro tvorbu query.
		/// Pokud je tabulka již v query připravena, skončí.
		/// </summary>
		/// <param name="currentEntity"></param>
		/// <param name="currentTable"></param>
		private void _AddNewTableIntoQuery(EntityTableGroup currentEntity, EntityTableOne currentTable)
		{
			if (currentTable.IsConnected) return;

			// Abych zajistil svoje zařazení do query, musím mít vytvořen svůj alias:
			currentTable.Alias = this._GetTableAlias(currentEntity.EntityNumber, currentTable.TableType);

			bool currentTableIsRoot = (currentEntity.EntityRootTableType == currentTable.TableType);
			if (!currentTableIsRoot)
				// Aktuální tabulka není Root tabulkou entity (jde například o tabulku položek, nebo master datovou tabulku):
				this._AddNewTableInner(currentEntity, currentTable);
			else if (!currentEntity.IsRoot)
				// Jsem Root tabulka své entity, ale entita není Root ve stromu tříd. 
				// Zajistím Join na parent entitu, pomocí Noris-vztahu mezi entitami:
				this._AddNewTableRelated(currentEntity, currentTable);
			else if (this.IsPhraseFrom)
				// Jsem Root entita, jsem její tabulka (nejsem zatím zapojen), a xistuje fráze FROM.
				// Tuto Root tabulku Root entity musím zapojit pomocí JOIN na některou vhodnou již zapojenou tabulku:
				this._AddNewRootTableJoin(currentEntity, currentTable);
			else
				// Jsem Root tabulka. Pokud jsem Root entitou, zajistím zapojení stylem FROM:
				this._JoinTableIntoQuery(currentEntity, currentTable, QueryEnlistType.From, null, null);
		}
		/// <summary>
		/// Zařadí danou tabulku do Query.
		/// Tabulka není Master tabulkou své třídy, budu řešit její Join na Master tabulku třídy.
		/// </summary>
		/// <param name="currentEntity"></param>
		/// <param name="currentTable"></param>
		private void _AddNewTableInner(EntityTableGroup currentEntity, EntityTableOne currentTable)
		{
			// Najdu parent tabulku (o jednu pozici vlevo: tedy pro Entries to bude Master, a pro Master to bude Subject):
			//  V případě entity, která je navázaná přes položkový vztah, je Left pořadí jiné, protože Root je tabulka Entries!
			EntityTableOne parentTable = currentEntity.GetLeftTable(currentTable);
			if (parentTable == null) return;

			// Zajistit přidání Parent tabulky do Query (zde se rozběhne rekurze):
			this._ConnectTableIntoQuery(currentEntity, parentTable);

			// Určit data pro JOIN a zajistit uložení tabulky do seznamu skutečně použitých tabulek:
			QueryEnlistType joinType;
			string joinClause, joinComment;
			currentEntity.DetectJoinData(currentTable, parentTable, out joinType, out joinClause, out joinComment);
			if (joinType == QueryEnlistType.LeftJoin || joinType == QueryEnlistType.FullJoin)
				this._JoinTableIntoQuery(currentEntity, currentTable, joinType, joinClause, joinComment);
		}
		/// <summary>
		/// Zařadí danou tabulku do Query.
		/// Tabulka je Master tabulkou podřízené třídy, v entitě je deklarován vztah na její parent entitu.
		/// Tabulku navážeme přes tento vztah.
		/// </summary>
		/// <param name="currentEntity"></param>
		/// <param name="currentTable"></param>
		private void _AddNewTableRelated(EntityTableGroup currentEntity, EntityTableOne currentTable)
		{
			// Najdu vztah na parent entitu, a najdu tu parent entitu:
			RelationInfo relInfo;
			EntityTableGroup parentEntity;
			if (!this._FindParentEntity(currentEntity, out relInfo, out parentEntity))
				Throw.SysError(_PrefixError + MessageInfo.Get("pro entitu %0 není možno najít vztah a/nebo parent entitu.", currentEntity.EntityNumber));

			// Podle typu vztahu vyhledám vazbu z parenta na current:
			switch (relInfo.RelationStatDyn)
			{
				case RelationStatDynType.Static:
					this._AddNewTableRelStat(currentEntity, currentTable, parentEntity, relInfo);
					break;
				case RelationStatDynType.Dynamic:
					this._AddNewTableRelDyn(currentEntity, currentTable, parentEntity, relInfo);
					break;
			}
		}
		/// <summary>
		/// Zařadí danou tabulku do Query.
		/// Tabulka je Master tabulkou Root entity, ale existuje fráze FROM.
		/// Proto se tato tabulka musí joinovat na jinou existující tabulku,
		/// která je součástí FROM.
		/// </summary>
		/// <param name="currentEntity"></param>
		/// <param name="currentTable"></param>
		private void _AddNewRootTableJoin(EntityTableGroup currentEntity, EntityTableOne currentTable)
		{
			// Najdu tabulku v naší entitě, která je součástí FROM:
			List<EntityTableOne> pivotTables = currentEntity.GetRightTables(currentTable); // Všechny tabulky vpravo ode mě
			EntityTableOne pivotTable = pivotTables.Find(t => t.IsInFrom);      // Nejbližší tabulka, která je součástí FROM
			if (pivotTable == null)
				Throw.SysError(MessageInfo.Get("Do daného SQL příkazu nelze aplikovat strom tříd, protože kořenovou tabulku stromu tříd nelze napojit na žádnou existující tabulku SQL příkazu."));

			// Určit data pro JOIN a zajistit uložení tabulky do seznamu skutečně použitých tabulek:
			QueryEnlistType joinType;
			string joinClause, joinComment;
			currentEntity.DetectJoinData(currentTable, pivotTable, out joinType, out joinClause, out joinComment);
			if (joinType == QueryEnlistType.LeftJoin || joinType == QueryEnlistType.FullJoin)
				this._JoinTableIntoQuery(currentEntity, currentTable, joinType, joinClause, joinComment);
		}
		/// <summary>
		/// Zajistí napojení dané tabulky do Query. Uloží potřebná data do this._UsedTableList
		/// </summary>
		/// <param name="entity">Entita, do které patří daná tabulka</param>
		/// <param name="table">Tabulka k přidání do Query</param>
		/// <param name="joinType">Typ přidání tabulky do Query (FROM, JOIN, LEFT OUTER JOIN)</param>
		/// <param name="joinClause">Výraz pro JOIN ON ...</param>
		/// <param name="joinComment">Komentář k JOINU</param>
		private void _JoinTableIntoQuery(EntityTableGroup entity, EntityTableOne table, QueryEnlistType joinType, string joinClause, string joinComment)
		{
			if (table.IsAddToQuery) return;
			this._UsedTableList.Add(new QueryUsedTables(entity.EntityNumber, joinType, table.TableName, table.Alias, joinClause, joinComment));
			table.IsAddToQuery = true;
		}
		/// <summary>
		/// Zajistí napojení dané tabulky do Query. Uloží potřebná data do this._UsedTableList
		/// </summary>
		/// <param name="entity">Entita, do které patří daná tabulka</param>
		/// <param name="tableName">Plný název tabulky</param>
		/// <param name="tableAlias">Alias tabulky, na něj se odkazuje joinClause</param>
		/// <param name="joinType">Typ přidání tabulky do Query (FROM, JOIN, LEFT OUTER JOIN)</param>
		/// <param name="joinClause">Výraz pro JOIN ON ...</param>
		/// <param name="joinComment">Komentář k JOINU</param>
		private void _JoinTableIntoQuery(EntityTableGroup entity, string tableName, string tableAlias, QueryEnlistType joinType, string joinClause, string joinComment)
		{
			this._UsedTableList.Add(new QueryUsedTables(entity.EntityNumber, joinType, tableName, tableAlias, joinClause, joinComment));
		}
		/// <summary>
		/// Vrátí alias pro určitou tabulku.
		/// Tato metoda nemůže být součástí třídy EntityTableGroup ani EntityTableOne, 
		/// protože pro generování aliasu se používá prefix this._PrefixAlias, a ten nebudu do subclass kopírovat.
		/// </summary>
		/// <param name="entityNumber">Číslo entity, jejíž tabulce se má generovat alias</param>
		/// <param name="tableType">Typ tabulky</param>
		/// <returns></returns>
		private string _GetTableAlias(int entityNumber, ClassTableType tableType)
		{
			string alias = this._PrefixAlias + "_" + entityNumber.ToString() + ClassInfo.ConvertTableTypeToString(tableType);
			return alias;
		}
		/// <summary>
		/// Vrátí alias pro tabulku dynamických vztahů.
		/// Tato metoda nemůže být součástí třídy EntityTableGroup ani EntityTableOne, 
		/// protože pro generování aliasu se používá prefix this._PrefixAlias, a ten nebudu do subclass kopírovat.
		/// </summary>
		/// <param name="entityNumber">Číslo entity, jejíž tabulce se má generovat alias</param>
		/// <returns></returns>
		private string _GetTableDynRelAlias(int entityNumber)
		{
			string alias = this._PrefixAlias + "_" + entityNumber.ToString() + "R";
			return alias;
		}
		/// <summary>
		/// Metoda vrátí název primárního sloupce v dané tabulce v dané entitě.
		/// Vrácený sloupec obsahuje alias dané tabulky, ne full name.
		/// Tato metoda nemůže (nesmí) detekovat, zda je tabulka IsUsed, ani nesmí volat _EnlistTableIntoQuery(),
		/// protože je volána zprostředka této metody (kdy je alias přidělen, ale tabulka ještě není zařazena v query).
		/// </summary>
		/// <param name="entity">Entita (třída, skupina tabulek)</param>
		/// <param name="table">Tabulka. Její alias bude uvedeno ve vráceném textu. Její typ bude použit pro získání názvu klíčového sloupce.</param>
		/// <returns></returns>
		private string _GetPrimaryKeyColumn(EntityTableGroup entity, EntityTableOne table)
		{
			return _GetPrimaryKeyColumn(entity, table, table.TableType);
		}
		/// <summary>
		/// Metoda vrátí název primárního sloupce v dané tabulce v dané entitě.
		/// Vrácený sloupec obsahuje alias dané tabulky, ne full name.
		/// Tato metoda nemůže (nesmí) detekovat, zda je tabulka IsUsed, ani nesmí volat _EnlistTableIntoQuery(),
		/// protože je volána zprostředka této metody (kdy je alias přidělen, ale tabulka ještě není zařazena v query).
		/// </summary>
		/// <param name="entity">Entita (třída, skupina tabulek)</param>
		/// <param name="table">Tabulka. Její alias bude uvedeno ve vráceném textu. Její typ bude použit pro získání názvu klíčového sloupce.</param>
		/// <param name="tableType">Typ klíče: je vhodné, když zjišťuji název klíče "cislo_subjektu" z položkové tabulky.</param>
		/// <returns></returns>
		private string _GetPrimaryKeyColumn(EntityTableGroup entity, EntityTableOne table, ClassTableType tableType)
		{
			// Sestavím a vrátím alias.primarykey:
			string identityColumn = entity.ClassData.GetIdentityColumn(tableType);
			if (String.IsNullOrEmpty(identityColumn))
				identityColumn = entity.ClassData.GetDefaultIdentityColumn(tableType);
			return table.Alias + "." + identityColumn;
		}
		#endregion
		#region VZTAHY - určení JOIN vztahů
		/// <summary>
		/// Určí JOIN výraz mezi parent tabulkou a current tabulkou, ve statickém vztahu
		/// </summary>
		/// <param name="currentEntity">Aktuální entita</param>
		/// <param name="currentTable">Aktuální tabulka</param>
		/// <param name="parentEntity">Parent entita</param>
		/// <param name="relInfo">Data o vztahu</param>
		/// <returns></returns>
		private void _AddNewTableRelStat(EntityTableGroup currentEntity, EntityTableOne currentTable, EntityTableGroup parentEntity, RelationInfo relInfo)
		{
			switch (currentEntity.RelationSide)
			{
				case RelationSideType.Left:
					// Vztah je zleva (parent je vlevo, ukazuje svým columnem na current doprava):
					this._AddNewTableRelStatLeftMaster(currentEntity, currentTable, parentEntity, relInfo);
					break;
				case RelationSideType.Right:
					// Vztah je zprava (current je vlevo, ukazuje svým columnem na parent doprava):
					this._AddNewTableRelStatRightMaster(currentEntity, currentTable, parentEntity, relInfo);
					break;
			}
		}
		/// <summary>
		/// Určí JOIN výraz mezi parent tabulkou a current tabulkou, v dynamickém vztahu
		/// </summary>
		/// <param name="currentEntity">Aktuální entita</param>
		/// <param name="currentTable">Aktuální tabulka</param>
		/// <param name="parentEntity">Parent entita</param>
		/// <param name="relInfo">Data o vztahu</param>
		/// <returns></returns>
		private void _AddNewTableRelDyn(EntityTableGroup currentEntity, EntityTableOne currentTable, EntityTableGroup parentEntity, RelationInfo relInfo)
		{
			bool isEntryRelation = (relInfo.RelationTyp == RelationTypType.ObjectSubject || relInfo.RelationTyp == RelationTypType.ObjectNonsubject);
			switch (currentEntity.RelationSide)
			{
				case RelationSideType.Left:
					// Vztah je zleva (parent je vlevo, ukazuje na dynamický vztah, dynamický vztah ukazuje doprava na current):
					if (!isEntryRelation)
						this._AddNewTableRelDynLeftMaster(currentEntity, currentTable, parentEntity, relInfo);
					else
						this._AddNewTableRelDynLeftEntry(currentEntity, currentTable, parentEntity, relInfo);
					break;
				case RelationSideType.Right:
					// Vztah je zprava (current je vlevo, ukazuje na dynamický vztah, dynamický vztah ukazuje doprava na parent):
					if (!isEntryRelation)
						this._AddNewTableRelDynRightMaster(currentEntity, currentTable, parentEntity, relInfo);
					else
						this._AddNewTableRelDynRightEntry(currentEntity, currentTable, parentEntity, relInfo);
					break;
			}
		}
		/// <summary>
		/// Určí JOIN výraz mezi parent tabulkou a current tabulkou, ve statickém vztahu.
		/// Parent je vlevo.
		/// Aktuální tabulka (currentTable) je vždy master tabulkou své třídy (lcs.subjekty nebo nonsubjektová tabulka).
		/// </summary>
		/// <param name="currentEntity">Aktuální entita</param>
		/// <param name="currentTable">Aktuální tabulka</param>
		/// <param name="parentEntity">Parent entita</param>
		/// <param name="relInfo">Data o vztahu</param>
		/// <returns></returns>
		private void _AddNewTableRelStatLeftMaster(EntityTableGroup currentEntity, EntityTableOne currentTable, EntityTableGroup parentEntity, RelationInfo relInfo)
		{
			// Vztah je zleva, klasický náhled na vztah, jen já sem vstupuji od konce vztahu.
			// parent bude vlevo, některým svým columnem (parentColumn) ukazuje na záznam slave tabulky, jehož klíč je v (currentKeyColumn):
			// current je vpravo, 
			// takže hledám vztah (current.key => parent.dbcolumn):
			string parentFullColumn = relInfo.ColumnName;                                       // "lcs.product_order.source"
			string currentKeyColumn = this._GetPrimaryKeyColumn(currentEntity, currentTable);   // "Fl123_1S.cislo_subjektu"
			EntityTableOne parentTable = parentEntity.FindTableWithColumn(parentFullColumn);    // Najde tabulku v parent třídě, která obsahuje sloupec, který je klíčem vztahu

			// Tady se určuje aliasovaný název db columnu z parent tabulky (zdroj vztahu).
			// Pokud parent tabulka ještě není v JOINu, napojí se nyní = rozběhne se rekurze:
			string parentAliasColumn = this._RenameColumnToAlias(parentEntity, parentTable, parentFullColumn);
			string joinClause = currentKeyColumn + " = " + parentAliasColumn;
			string joinComment = "SR=" + relInfo.CisloVztahu.ToString() + "," + RelationInfo.ConvertRelationSideToString(currentEntity.EntityData.RelationSide);

			// Zařadím tabulku do query:
			this._JoinTableIntoQuery(currentEntity, currentTable, QueryEnlistType.LeftJoin, joinClause, joinComment);
		}
		/// <summary>
		/// Určí JOIN výraz mezi parent tabulkou a current tabulkou, ve statickém vztahu.
		/// Parent je vpravo.
		/// </summary>
		/// <param name="currentEntity">Aktuální entita</param>
		/// <param name="currentTable">Aktuální tabulka</param>
		/// <param name="parentEntity">Parent entita</param>
		/// <param name="relInfo">Data o vztahu</param>
		/// <returns></returns>
		private void _AddNewTableRelStatRightMaster(EntityTableGroup currentEntity, EntityTableOne currentTable, EntityTableGroup parentEntity, RelationInfo relInfo)
		{
			// Vztah je zprava, jde o reverzní pohled na vztah: 
			// parent bude vpravo
			// current je vlevo, a svým sloupcem relInfo.ColumnName ukazuje na parenta doprava, klíč parenta je v jeho datech:
			// takže hledám vztah (current.dbcolumn <= parent.key)
			string currentAliasColumn = currentTable.RenameColumnToAlias(relInfo.ColumnName);   // current sloupec, obsahuje klíč slave záznamu, jímž je parent
			EntityTableOne parentTable = parentEntity.GetRootTable();                           // Najde tabulku, která je Root v parent třídě

			// Tady zajistím zapojení parent tabulky do Query, tím získá tabulka svůj alias.
			// Tím se může rozběhnout rekurzivní zařazování tabulek do query.
			// Pak se mohu na alias odkazovat a použít ho v JOINu:
			this._ConnectTableIntoQuery(parentEntity, parentTable);
			string parentAliasColumn = this._GetPrimaryKeyColumn(parentEntity, parentTable, ClassTableType.Master);    // "Fl123_1S.cislo_subjektu"
			string joinClause = currentAliasColumn + " = " + parentAliasColumn;
			string joinComment = "SR=" + relInfo.CisloVztahu.ToString() + "," + RelationInfo.ConvertRelationSideToString(currentEntity.EntityData.RelationSide);

			// Zařadím tabulku do query:
			this._JoinTableIntoQuery(currentEntity, currentTable, QueryEnlistType.LeftJoin, joinClause, joinComment);
		}
		/// <summary>
		/// Určí JOIN výraz mezi parent tabulkou a current tabulkou, v dynamickém vztahu.
		/// Parent je vlevo, jde o vztah hlavičkový.
		/// </summary>
		/// <param name="currentEntity">Aktuální entita</param>
		/// <param name="currentTable">Aktuální tabulka</param>
		/// <param name="parentEntity">Parent entita</param>
		/// <param name="relInfo">Data o vztahu</param>
		/// <returns></returns>
		private void _AddNewTableRelDynLeftMaster(EntityTableGroup currentEntity, EntityTableOne currentTable, EntityTableGroup parentEntity, RelationInfo relInfo)
		{
			// Vztah je zleva, klasický náhled na vztah, jen já sem vstupuji od konce vztahu.
			// parent bude vlevo, ukazuje (svým klíčem) do vztahové tabulky lcs.vztahysubjektu do sloupce cislo_subjektu,
			// vztahová tabulka lcs.vztahysubjektu (filtruje se číslem vztahu) 
			// pak ukazuje doprava sloupcem cislo_vztaz_subjektu na klíč naší current tabulky, která je vpravo.
			string dvAlias = this._GetTableDynRelAlias(currentEntity.EntityNumber);     // Alias tabulky vztahů: prefix, číslo entity, "R"
			string joinClause, joinComment;

			// Potřebuji mít zapojenou master tabulku parent entity:
			EntityTableOne parentTable = parentEntity.GetRootTable();                  // Najde tabulku, která je Root v parent třídě
			this._ConnectTableIntoQuery(parentEntity, parentTable);                     // Pokud parent tabulka ještě není v JOINu, napojí se nyní = rozběhne se rekurze:
			string parentAliasColumn = this._GetPrimaryKeyColumn(parentEntity, parentTable, ClassTableType.Master);    // "Fl123_1S.cislo_subjektu"

			// K master tabulce parent třídy zapojím tabulku dynamických vztahů:
			// JOIN .. ON "dv.cislo_subjektu = parent.key and dv.cislo_vztahu = REL":   (závorky se doplní na závěr)
			joinClause = dvAlias + ".cislo_subjektu = " + parentAliasColumn + " and " + dvAlias + ".cislo_vztahu = " + relInfo.CisloVztahu.ToString();
			joinComment = "DR=" + relInfo.Nazev;
			this._JoinTableIntoQuery(currentEntity, "lcs.vztahysubjektu", dvAlias, QueryEnlistType.LeftJoin, joinClause, joinComment);

			// Nyní mohu current tabulku joinovat k tabulce vztahů:
			string currentKeyColumn = this._GetPrimaryKeyColumn(currentEntity, currentTable);   // "Fl123_1S.cislo_subjektu"
			joinClause = currentKeyColumn + " = " + dvAlias + ".cislo_vztaz_subjektu";        // JOIN .. ON "current.key = dv.cislo_vztaz_subjektu"
			joinComment = "DR=" + relInfo.CisloVztahu.ToString() + "," + RelationInfo.ConvertRelationSideToString(currentEntity.EntityData.RelationSide);

			// Zařadím tabulku do query:
			this._JoinTableIntoQuery(currentEntity, currentTable, QueryEnlistType.LeftJoin, joinClause, joinComment);
		}
		/// <summary>
		/// Určí JOIN výraz mezi parent tabulkou a current tabulkou, v dynamickém vztahu.
		/// Parent je vlevo, jde o vztah položkový.
		/// </summary>
		/// <param name="currentEntity">Aktuální entita</param>
		/// <param name="currentTable">Aktuální tabulka</param>
		/// <param name="parentEntity">Parent entita</param>
		/// <param name="relInfo">Data o vztahu</param>
		/// <returns></returns>
		private void _AddNewTableRelDynLeftEntry(EntityTableGroup currentEntity, EntityTableOne currentTable, EntityTableGroup parentEntity, RelationInfo relInfo)
		{
			// Vztah je zleva, klasický náhled na vztah, jen já sem vstupuji od konce vztahu.
			// parent bude vlevo, ukazuje (svým klíčem) do vztahové tabulky lcs.objektsubjekt do sloupců cislo_dokladu a cislo_objektu,
			// vztahová tabulka lcs.objektsubjekt (filtruje se číslem vztahu) 
			// pak ukazuje doprava sloupcem cislo_subjektu na klíč naší current tabulky, která je vpravo.
			string dvAlias = this._GetTableDynRelAlias(currentEntity.EntityNumber);     // Alias tabulky vztahů: prefix, číslo entity, "R"
			string joinClause, joinComment;

			// Potřebuji mít zapojenou entries tabulku parent entity:
			MessageInfo errText = MessageInfo.Get("Chyba v definici vztahu %0 %1: je definován jako položkový, ale jeho master třída %2 %3 nemá tabulku položek.", relInfo.CisloVztahu, relInfo.Nazev, parentEntity.ClassData.CisloTridy, parentEntity.ClassData.NazevTridy);
			EntityTableOne parentTable = parentEntity.FindTableOfType(ClassTableType.Entries, errText);  // Najde tabulku, která je Entries v parent třídě (pokud nenajde, vyhodí připravenou chybu)
			this._ConnectTableIntoQuery(parentEntity, parentTable);                     // Pokud parent tabulka ještě není v JOINu, napojí se nyní = rozběhne se rekurze (Entries => Master => Subjekty => Vyšší entita...)

			string parentEAliasColumn = this._GetPrimaryKeyColumn(parentEntity, parentTable, ClassTableType.Entries);   // "Fl123_1E.cislo_objektu" 
			string parentSAliasColumn = this._GetPrimaryKeyColumn(parentEntity, parentTable, ClassTableType.Master);    // "Fl123_1E.cislo_subjektu"

			// K entries tabulce parent třídy zapojím tabulku položkových dynamických vztahů:
			// JOIN .. ON "dv.cislo_subjektu = parent.key and dv.cislo_vztahu = REL":   (závorky se doplní na závěr)
			joinClause = dvAlias + ".cislo_dokladu = " + parentSAliasColumn + " and " + dvAlias + ".cislo_objektu = " + parentEAliasColumn + " and " + dvAlias + ".cislo_vztahu = " + relInfo.CisloVztahu.ToString();
			joinComment = "DR=" + relInfo.Nazev;
			this._JoinTableIntoQuery(currentEntity, "lcs.objektsubjekt", dvAlias, QueryEnlistType.LeftJoin, joinClause, joinComment);

			// Nyní mohu current tabulku joinovat k tabulce vztahů:
			string currentKeyColumn = this._GetPrimaryKeyColumn(currentEntity, currentTable);   // "Fl123_1S.cislo_subjektu"
			joinClause = currentKeyColumn + " = " + dvAlias + ".cislo_subjektu";              // JOIN .. ON "current.key = dv.cislo_subjektu"
			joinComment = "DR=" + relInfo.CisloVztahu.ToString() + "," + RelationInfo.ConvertRelationSideToString(currentEntity.EntityData.RelationSide);

			// Zařadím tabulku do query:
			this._JoinTableIntoQuery(currentEntity, currentTable, QueryEnlistType.LeftJoin, joinClause, joinComment);
		}
		/// <summary>
		/// Určí JOIN výraz mezi parent tabulkou a current tabulkou, v dynamickém vztahu.
		/// Parent je vpravo, jde o vztah hlavičkový.
		/// </summary>
		/// <param name="currentEntity">Aktuální entita</param>
		/// <param name="currentTable">Aktuální tabulka</param>
		/// <param name="parentEntity">Parent entita</param>
		/// <param name="relInfo">Data o vztahu</param>
		/// <returns></returns>
		private void _AddNewTableRelDynRightMaster(EntityTableGroup currentEntity, EntityTableOne currentTable, EntityTableGroup parentEntity, RelationInfo relInfo)
		{
			// Vztah je zprava, opačný náhled na vztah, ale já sem vstupuji od konce vztahu.
			// parent (entries) bude vpravo, ukazuje (svým klíčem) do vztahové tabulky lcs.vztahysubjektu do sloupce cislo_vztaz_subjektu
			// vztahová tabulka lcs.vztahysubjektu (filtruje se číslem vztahu) 
			// pak ukazuje doleva sloupcem cislo_subjektu, na klíč naší current tabulky, která je vpravo.
			string dvAlias = this._GetTableDynRelAlias(currentEntity.EntityNumber);     // Alias tabulky vztahů: prefix, číslo entity, "R"
			string joinClause, joinComment;

			// Potřebuji mít zapojenou master tabulku parent entity:
			EntityTableOne parentTable = parentEntity.GetRootTable();                  // Najde tabulku, která je Root v parent třídě
			this._ConnectTableIntoQuery(parentEntity, parentTable);                     // Pokud parent tabulka ještě není v JOINu, napojí se nyní = rozběhne se rekurze:
			string parentAliasColumn = this._GetPrimaryKeyColumn(parentEntity, parentTable, ClassTableType.Master);    // "Fl123_1S.cislo_subjektu"

			// K master tabulce parent třídy zapojím tabulku dynamických vztahů:
			// JOIN .. ON "dv.cislo_vztaz_subjektu = parent.key and dv.cislo_vztahu = REL":   (závorky se doplní na závěr)
			joinClause = dvAlias + ".cislo_vztaz_subjektu = " + parentAliasColumn + " and " + dvAlias + ".cislo_vztahu = " + relInfo.CisloVztahu.ToString();
			joinComment = "DR=" + relInfo.Nazev;
			this._JoinTableIntoQuery(currentEntity, "lcs.vztahysubjektu", dvAlias, QueryEnlistType.LeftJoin, joinClause, joinComment);

			// Nyní mohu current tabulku joinovat k tabulce vztahů:
			string currentKeyColumn = this._GetPrimaryKeyColumn(currentEntity, currentTable);   // "Fl123_1S.cislo_subjektu"
			joinClause = currentKeyColumn + " = " + dvAlias + ".cislo_subjektu";              // JOIN .. ON "current.key = dv.cislo_subjektu"
			joinComment = "DR=" + relInfo.CisloVztahu.ToString() + "," + RelationInfo.ConvertRelationSideToString(currentEntity.EntityData.RelationSide);

			// Zařadím tabulku do query:
			this._JoinTableIntoQuery(currentEntity, currentTable, QueryEnlistType.LeftJoin, joinClause, joinComment);
		}
		/// <summary>
		/// Určí JOIN výraz mezi parent tabulkou a current tabulkou, v dynamickém vztahu.
		/// Parent je vpravo, jde o vztah položkový.
		/// </summary>
		/// <param name="currentEntity">Aktuální entita</param>
		/// <param name="currentTable">Aktuální tabulka</param>
		/// <param name="parentEntity">Parent entita</param>
		/// <param name="relInfo">Data o vztahu</param>
		/// <returns></returns>
		private void _AddNewTableRelDynRightEntry(EntityTableGroup currentEntity, EntityTableOne currentTable, EntityTableGroup parentEntity, RelationInfo relInfo)
		{
			// Vztah je zprava, opačný náhled na vztah, ale já sem vstupuji od konce vztahu.
			// parent (master) bude vpravo, ukazuje (svým klíčem) do vztahové tabulky lcs.objektsubjekt do sloupce cislo_subjektu
			// vztahová tabulka lcs.objektsubjekt (filtruje se číslem vztahu) 
			// pak ukazuje doleva sloupci cislo_dokladu a cislo_objektu na klíč naší current (entries) tabulky, která je vlevo.
			string dvAlias = this._GetTableDynRelAlias(currentEntity.EntityNumber);     // Alias tabulky vztahů: prefix, číslo entity, "R"
			string joinClause, joinComment;

			// Potřebuji mít zapojenou master tabulku parent entity (protože je vpravo, musí to být master, vpravo nemůže být položka!):
			EntityTableOne parentTable = parentEntity.GetRootTable();                  // Najde tabulku, která je Root v parent třídě
			this._ConnectTableIntoQuery(parentEntity, parentTable);                     // Pokud parent tabulka ještě není v JOINu, napojí se nyní = rozběhne se rekurze
			string parentAliasColumn = this._GetPrimaryKeyColumn(parentEntity, parentTable, ClassTableType.Master);   // "Fl123_1E.cislo_subjektu"

			// K master tabulce parent třídy zapojím tabulku položkových dynamických vztahů:
			// JOIN .. ON "dv.cislo_subjektu = parent.key and dv.cislo_vztahu = REL":   (závorky se doplní na závěr)
			joinClause = dvAlias + ".cislo_subjektu = " + parentAliasColumn + " and " + dvAlias + ".cislo_vztahu = " + relInfo.CisloVztahu.ToString();
			joinComment = "DR=" + relInfo.Nazev;
			this._JoinTableIntoQuery(currentEntity, "lcs.objektsubjekt", dvAlias, QueryEnlistType.LeftJoin, joinClause, joinComment);

			// Nyní mohu current tabulku (Entries) joinovat k tabulce vztahů:
			string currentSKeyColumn = this._GetPrimaryKeyColumn(currentEntity, currentTable, ClassTableType.Master);   // "Fl123_1E.cislo_subjektu"
			string currentEKeyColumn = this._GetPrimaryKeyColumn(currentEntity, currentTable, ClassTableType.Entries);  // "Fl123_1E.cislo_objektu"
			// JOIN .. ON "current.subjekt = dv.cislo_dokladu and current.objekt = dv.cislo_objektu"
			joinClause = currentSKeyColumn + " = " + dvAlias + ".cislo_dokladu and " + currentEKeyColumn + " = " + dvAlias + ".cislo_objektu";
			joinComment = "DR=" + relInfo.CisloVztahu.ToString() + "," + RelationInfo.ConvertRelationSideToString(currentEntity.EntityData.RelationSide);

			// Zařadím tabulku do query:
			this._JoinTableIntoQuery(currentEntity, currentTable, QueryEnlistType.LeftJoin, joinClause, joinComment);
		}
		#endregion
		#region VNITŘNÍ TŘÍDA QueryUsedTables
		/// <summary>
		/// Informace o jedné tabulce, která je zařazena v query
		/// </summary>
		protected class QueryUsedTables
		{
			/// <summary>
			/// Konstruktor
			/// </summary>
			/// <param name="entityNumber"></param>
			/// <param name="queryEnlist"></param>
			/// <param name="tableName"></param>
			/// <param name="tableAlias"></param>
			/// <param name="joinClause"></param>
			internal QueryUsedTables(int entityNumber, QueryEnlistType queryEnlist, string tableName, string tableAlias, string joinClause, string joinComment)
			{
				this.EntityNumber = entityNumber;
				this.QueryEnlist = queryEnlist;
				this.TableName = tableName;
				this.TableAlias = tableAlias;
				this.JoinClause = joinClause;
				this.JoinComment = joinComment;
			}
			/// <summary>
			/// Vizualizace
			/// </summary>
			/// <returns></returns>
			public override string ToString()
			{
				switch (this.QueryEnlist)
				{
					case QueryEnlistType.From:
						return "FROM " + this.TableName + QueryPhraseAlias;
					case QueryEnlistType.FullJoin:
						return " JOIN " + this.TableName + QueryPhraseAlias + QueryPhraseJoinClause;
					case QueryEnlistType.LeftJoin:
						return " LEFT OUTER JOIN " + this.TableName + QueryPhraseAlias + QueryPhraseJoinClause;
				}
				return "NotEnlist " + this.TableName;
			}
			/// <summary>
			/// Číslo entity
			/// </summary>
			public int EntityNumber { get; protected set; }
			/// <summary>
			/// Způsob zařazení tabulky do query
			/// </summary>
			public QueryEnlistType QueryEnlist { get; protected set; }
			/// <summary>
			/// Plný název tabulky včetně prefixu vlastníka
			/// </summary>
			public string TableName { get; protected set; }
			/// <summary>
			/// Alias tabulky
			/// </summary>
			public string TableAlias { get; protected set; }
			/// <summary>
			/// Výraz pro napojení této tabulky do query (podmínka do fráze JOIN ON ...)
			/// </summary>
			public string JoinClause { get; protected set; }
			/// <summary>
			/// Informace (komentář) do JOINu, popisující ve zkratce důvod joinu (číslo vztahu, strana).
			/// </summary>
			public string JoinComment { get; set; }
			/// <summary>
			/// Obsahuje frázi ALIAS do query (buď empty string, nebo " alias").
			/// </summary>
			protected string QueryPhraseAlias { get { return (this.TableAlias.IsEmpty() ? "" : " " + this.TableAlias); } }
			/// <summary>
			/// Obsahuje frázi JOINCLAUSE do query (buď empty string, nebo " alias").
			/// </summary>
			protected string QueryPhraseJoinClause { get { return (this.JoinClause.IsEmpty() ? "" : " ON (" + this.JoinClause) + ")"; } }
			/// <summary>
			/// Obsahuje KOMENTÁŘ do fráze JOIN ON ...(buď empty string, nebo " /* r=9260,L */)".
			/// </summary>
			protected string QueryPhraseJoinComment { get { return (this.JoinComment.IsEmpty() ? "" : " " + SqlFormat.ToComment(this.JoinComment) + " "); } }
			/// <summary>
			/// Vrací text o zařazení této tabulky do query.
			/// </summary>
			/// <returns></returns>
			internal string ToQuery()
			{
				switch (this.QueryEnlist)
				{
					case QueryEnlistType.From:
						return "FROM " + this.TableName + QueryPhraseAlias;
					case QueryEnlistType.FullJoin:
						return " JOIN " + this.TableName + QueryPhraseAlias + QueryPhraseJoinClause + QueryPhraseJoinComment;
					case QueryEnlistType.LeftJoin:
						return " LEFT OUTER JOIN " + this.TableName + QueryPhraseAlias + QueryPhraseJoinClause + QueryPhraseJoinComment;
				}
				return "";
			}
		}
		#endregion
        #region class QueryTablesLink : informace o jednom postupně dohledávaném vztahu dvou entit
        /// <summary>
        /// QueryTablesLink : informace o jednom postupně dohledávaném vztahu dvou entit.
        /// Vztahy se hledají odspodu, od konce stromu, směrem vzhůru = k frázi FROM.
        /// Dolní část je vždy jistá, směrem nahoru se některé kroky mohou někdy přeskakovat: 
        /// Pokud máme ve stromu tříd následující konstrukci: Root 1 - vztah doprava na Číselník A - vztah zprava na číselník A z třídy 2,
        /// pak v podstatě žádnou tabulku pro číselník A nepotřebuji, z obou stran vztahu na záznam číselníku ukazuje klíč vztahu, 
        /// takže mohu dát JOIN z Root1 přímo na třídu 2.
        /// </summary>
        protected class QueryTablesLink
        {
            /// <summary>
            /// Dolní část linku: tabulka (nese v sobě dolní entitu = třídu, vztah, stranu vztahu)
            /// </summary>
            public EntityTableOne DownTable { get; private set; }
            /// <summary>
            /// Dolní část linku: je to klíč záznamu? (subjekt, nonsubjekt)
            /// </summary>
            public bool DownIsKey { get; private set; }
            /// <summary>
            /// Dolní část linku: název sloupce včetně aliasu z tabulky, který představuje vztah (například Fl26_3M.order_relation)
            /// </summary>
            public string DownColumn { get; private set; }
            /// <summary>
            /// Horní část linku: je to klíč záznamu?
            /// </summary>
            public bool UpIsKey { get; set; }
            /// <summary>
            /// Horní část linku: tabulka v Up entitě
            /// </summary>
            public EntityTableOne UpTable { get; set; }
            /// <summary>
            /// Horní část linku: název sloupce včetně aliasu
            /// </summary>
            public string UpColumn { get; set; }
        }
        #endregion
    }
	#region PODPŮRNÉ TŘÍDY
	/// <summary>
	/// Jedna entita v rámci stromu tříd. Entita je v podstatě třídou, ale tato třída v jednom stromě se může vyskytovat ve více entitách
	/// (například v jednom stromů může být entita 2 pro sklad VÝDEJ, a entita 3 pro sklad PŘÍJEM).
	/// Každá entita používá jiné aliasy.
	/// </summary>
	internal class EntityTableGroup
	{
		/// <summary>
		/// Vytvoří záznam entity pro daná data o entitě
		/// </summary>
		/// <param name="entityData">Vstupující data o entitě</param>
		/// <returns></returns>
		internal static EntityTableGroup CreateForEntity(INorisClassTreeEntity entityData)
		{
			if (entityData == null) return null;
			ClassInfo classData = null;
			if (!Repository.Classes.TryGetValue(entityData.ClassNumber, out classData))
			{
				FolderInfo folderData;
				if (Repository.Folders.TryGetValue(entityData.FolderNumber, out folderData))
					classData = folderData.ClassData;
			}
			if (classData == null) return null;
			return new EntityTableGroup(entityData.EntityNumber, entityData, classData);
		}
		/// <summary>
		/// Vytvoří záznam entity pro daná data o třídě
		/// </summary>
		/// <param name="classData"></param>
		/// <returns></returns>
		internal static EntityTableGroup CreateForClass(ClassInfo classData)
		{
			if (classData == null) return null;
			return new EntityTableGroup(1, null, classData);
		}
		/// <summary>
		/// Konstruktor
		/// </summary>
		/// <param name="entityNumber"></param>
		/// <param name="entityData"></param>
		/// <param name="classData"></param>
		private EntityTableGroup(int entityNumber, INorisClassTreeEntity entityData, ClassInfo classData)
		{
			this.EntityNumber = entityNumber;
			this.EntityData = entityData;
			this.ClassData = classData;
			this.TableDict = new Dictionary<ClassTableType, EntityTableOne>();

			// Toto provádět až po naplnění this:
			if (classData.IsSubject)
				this.TableDict.Add(ClassTableType.Subjekt, new EntityTableOne(this, ClassTableType.Subjekt, "lcs.subjekty"));

			this.TableDict.Add(ClassTableType.Master, new EntityTableOne(this, ClassTableType.Master, classData.GetTableName(ClassTableType.Master)));

			if (classData.HasEntries)
				this.TableDict.Add(ClassTableType.Entries, new EntityTableOne(this, ClassTableType.Entries, classData.GetTableName(ClassTableType.Entries)));

			if (classData.HasUdaMaster)
				this.TableDict.Add(ClassTableType.UdaMaster, new EntityTableOne(this, ClassTableType.UdaMaster, classData.GetTableName(ClassTableType.UdaMaster)));

			if (classData.HasUdaEntries)
				this.TableDict.Add(ClassTableType.UdaEntries, new EntityTableOne(this, ClassTableType.UdaEntries, classData.GetTableName(ClassTableType.UdaEntries)));
		}
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "EntityNumber=" + this.EntityNumber + "; Class=" + this.ClassData.ToString();
		}
		/// <summary>
		/// Data entity = externí, načtená ze stromu tříd v Greenu
		/// </summary>
		public INorisClassTreeEntity EntityData { get; protected set; }
		/// <summary>
		/// Číslo entity
		/// </summary>
		public int EntityNumber { get; protected set; }
		/// <summary>
		/// Data třídy
		/// </summary>
		public ClassInfo ClassData { get; protected set; }
		/// <summary>
		/// Název sloupce, který nese klíč této třídy (cislo_subjektu, cislo_nonsubjektu).
		/// </summary>
		public string KeyColumn
		{ get { return this.ClassData.MasterIdentityColumn; } }
		/// <summary>
		/// Je toto Root entita?   true = je to Root, nemá parenta.
		/// </summary>
		public bool IsRoot { get { return !this.HasParent; } }
		/// <summary>
		/// Má tato entita určeného parenta?   true = má parenta / false = nemá
		/// </summary>
		public bool HasParent { get { return (this.EntityData != null && this.EntityData.ParentEntityNumber > 0); } }
		/// <summary>
		/// Má tato entita určený vztah?   true = má / false = nemá
		/// </summary>
		public bool HasRelation { get { return (this.EntityData != null && this.EntityData.RelationNumber > 0); } }
		/// <summary>
		/// Číslo parent entity, nebo 0
		/// </summary>
		public int ParentEntityNumber { get { return (this.EntityData != null ? this.EntityData.ParentEntityNumber : 0); } }
		/// <summary>
		/// Číslo vztahu z parent entity na mě
		/// </summary>
		public int RelationNumber { get { return (this.EntityData != null ? this.EntityData.RelationNumber : 0); } }
		/// <summary>
		/// Strana vztahu této entity
		/// </summary>
		public RelationSideType RelationSide { get { return (this.EntityData != null ? this.EntityData.RelationSide : RelationSideType.None); } }
		/// <summary>
		/// Typový Atribut: Druh vztahu {Statický; Dynamický}
		/// </summary>
		public RelationStatDynType RelationStatDyn { get { return (this.HasRelation ? this.RelationData.RelationStatDyn : RelationStatDynType.None); } }
		/// <summary>
		/// Typový Atribut: Typ vztahu {"SS"=Subjekt - Subjekt; "SN"=Subjekt - Nonsubjekt; "OS"=Položka - Subjekt; "ON"=Položka - Nonsubjekt; "NS"=Nonsubjekt - Subjekt; "NN"=Nonsubjekt - Nonsubjekt}
		/// </summary>
		public RelationTypType RelationTyp { get { return (this.HasRelation ? this.RelationData.RelationTyp : RelationTypType.None); } }
		/// <summary>
		/// Příznak, že vztah je položkový
		/// </summary>
		public bool RelationIsEntries
		{
			get
			{
				RelationTypType relationTyp = this.RelationTyp;
				return (relationTyp == RelationTypType.ObjectSubject || relationTyp == RelationTypType.ObjectNonsubject);
			}
		}
		/// <summary>
		/// Typ tabulky, která je pro tuto konkrétní entitu kořenem do Joinů.
		/// Zdálo by se, že je to Subject nebo Master, ale výjimečně to může být i Entries.
		/// Pokud se tato entita jako podřízená váže k parent entitě přes položkový vztah, pak EntityRootClassType je Entries!!!
		/// Tomu jsou uzpůsobeny i další metody této třídy, typicky metody pro vracení Left a Right tabulek, a metoda pro vracení MasterTable.
		/// </summary>
		public ClassTableType EntityRootTableType
		{
			get
			{
				ClassTableType root = this.ClassData.MasterTableType;   // Běžně je root tabulkou master tabulka třídy
				if (this.HasParent && this.HasRelation && this.ClassData.IsEntries)
				{	// Pokud mám parenta a vztah a jde o položkovou třídu, zjistím zda to není položkový vztah zprava:
					if (this.RelationIsEntries && this.RelationSide == RelationSideType.Right)
						root = ClassTableType.Entries;
				}
				return root;
			}
		}
		/// <summary>
		/// Data o vztahu z parenta na mě
		/// </summary>
		public RelationInfo RelationData
		{
			get
			{
				if (!this.HasRelation) return null;
				if (this._RelationData == null && this.EntityData != null)
					this._RelationData = Repository.Relations[this.EntityData.RelationNumber];
				return this._RelationData;
			}
		}
		private RelationInfo _RelationData;
		/// <summary>
		/// Tabulky třídy zde použité entity, včetně příznaku použití a aliasu
		/// </summary>
		public Dictionary<ClassTableType, EntityTableOne> TableDict { get; protected set; }
		/// <summary>
		/// Metoda se pokusí najít některou tabulku v rámci této entity, která by mohla obsahovat hledaný db column.
		/// </summary>
		/// <param name="fullDbColumnName"></param>
		/// <returns></returns>
		internal EntityTableOne FindTableWithColumn(string fullDbColumnName)
		{
			EntityTableOne tableOne = this.TryFindTableWithColumn(fullDbColumnName);
			if (tableOne == null)
				Throw.SysError(MessageInfo.Get("V entitě %0 (pro filtr %1, třída entity %2) nelze najít tabulku pro sloupec %3.",
					this.EntityData.EntityNumber, this.EntityData.ClassNumber, this.ClassData.ToString(), fullDbColumnName));
			return tableOne;
		}
		/// <summary>
		/// Metoda se pokusí najít některou tabulku v rámci této entity, která by mohla obsahovat hledaný db column.
		/// </summary>
		/// <param name="fullDbColumnName"></param>
		/// <returns></returns>
		internal EntityTableOne TryFindTableWithColumn(string fullDbColumnName)
		{
			string masterTableName = null;
			EntityTableOne masterTable;
			if (this.TableDict.TryGetValue(ClassTableType.Master, out masterTable))
				masterTableName = masterTable.TableName;
			foreach (EntityTableOne tableAlias in this.TableDict.Values)
			{
				if (tableAlias.IsColumnInThisTable(fullDbColumnName, masterTableName))
					return tableAlias;
			}
			return null;
		}
		/// <summary>
		/// Najdi a vrať tabulku daného typu, nebo vyhoď chybu
		/// </summary>
		/// <param name="tableType">Typ hledané tabulky</param>
		/// <returns></returns>
		internal EntityTableOne FindTableOfType(ClassTableType tableType)
		{
			return FindTableOfType(tableType,
				MessageInfo.Get("Entita %0 pro třídu %1 neobsahuje data pro tabulku typu %2.", this.EntityNumber, this.ClassData.ToString(), tableType.ToString()));
		}
		/// <summary>
		/// Najdi a vrať tabulku daného typu, nebo vyhoď chybu
		/// </summary>
		/// <param name="tableType">Typ hledané tabulky</param>
		/// <param name="errText">Předem připravený text pro případ, když by došlo k chybě</param>
		/// <returns></returns>
		internal EntityTableOne FindTableOfType(ClassTableType tableType, MessageInfo errText)
		{
			EntityTableOne tableOne;
			if (!this.TableDict.TryGetValue(tableType, out tableOne))
				Throw.SysError(errText);
			return tableOne;
		}
		/// <summary>
		/// Najde a vrátí tabulku, která je master tabulkou v této entitě.
		/// Je to tabulka typu (this.EntityRootClassType), tedy Subject, nebo Master, nebo Entries.
		/// Záleží na typu třídy, na pozici entity a na vztahu na parent entitu.
		/// </summary>
		/// <returns></returns>
		internal EntityTableOne GetRootTable()
		{
			return this.FindTableOfType(this.EntityRootTableType);
		}
		/// <summary>
		/// Vrátí seznam všech tabulek, které jsou obsaženy v této entitě = třídě.
		/// Vrací new objekt, izolovaná kopie interního seznamu.
		/// </summary>
		/// <returns></returns>
		internal List<EntityTableOne> GetAllTables()
		{
			return new List<EntityTableOne>(this.TableDict.Values);
		}
		/// <summary>
		/// Najde a vrátí tabulku, která je vlevo od dané tabulky ve struktuře jedné třídy.
		/// </summary>
		/// <param name="currentTable">Výchozí tabulka</param>
		/// <returns></returns>
		internal EntityTableOne GetLeftTable(EntityTableOne currentTable)
		{
			if (currentTable == null) return null;
			return this.GetLeftTable(currentTable.TableType);
		}
		/// <summary>
		/// Najde a vrátí tabulku, která je vlevo od dané tabulky (dané typem) ve struktuře jedné třídy.
		/// </summary>
		/// <param name="currentTableType">Typ výchozí tabulky</param>
		/// <returns></returns>
		internal EntityTableOne GetLeftTable(ClassTableType currentTableType)
		{
			List<EntityTableOne> leftTables = this.GetLeftTables(currentTableType);   // Tato metoda řeší nejen správné pořadí, ale i existenci tabulek v rámci entity!
			if (leftTables.Count == 0) return null;
			return leftTables[0];
		}
		/// <summary>
		/// Najde a vrátí seznam tabulek, které existují (měly by existovat) a jsou VLEVO od dané tabulky.
		/// </summary>
		/// <param name="currentTable">Výchozí tabulka</param>
		/// <returns></returns>
		internal List<EntityTableOne> GetLeftTables(EntityTableOne currentTable)
		{
			if (currentTable == null) return new List<EntityTableOne>();
			return this.GetLeftTables(currentTable.TableType);
		}
		/// <summary>
		/// Najde a vrátí seznam tabulek, které existují (měly by existovat) a jsou VLEVO od dané tabulky.
		/// </summary>
		/// <param name="currentTableType">Typ výchozí tabulky</param>
		/// <returns></returns>
		internal List<EntityTableOne> GetLeftTables(ClassTableType currentTableType)
		{
			bool rootIsEntries = (this.EntityRootTableType == ClassTableType.Entries);      // true, pokud rootem je Entries
			List<EntityTableOne> result = new List<EntityTableOne>();
			if (rootIsEntries)
			{	// Pokud Root tabulkou jsou Entries (entita je navázaná položkovým vztahem):
				switch (currentTableType)
				{
					case ClassTableType.Subjekt:
						this._AddTableOfType(ClassTableType.Entries, result);
						break;
					case ClassTableType.Master:
						this._AddTableOfType(ClassTableType.Subjekt, result);
						this._AddTableOfType(ClassTableType.Entries, result);
						break;
					case ClassTableType.UdaMaster:
						this._AddTableOfType(ClassTableType.Master, result);
						this._AddTableOfType(ClassTableType.Subjekt, result);
						this._AddTableOfType(ClassTableType.Entries, result);
						break;
					case ClassTableType.Entries:
						break;
					case ClassTableType.UdaEntries:
						this._AddTableOfType(ClassTableType.Entries, result);
						break;
				}
			}
			else
			{	// Root tabulkou je Subjekt / Master:
				switch (currentTableType)
				{
					case ClassTableType.Subjekt:
						break;
					case ClassTableType.Master:
						this._AddTableOfType(ClassTableType.Subjekt, result);
						break;
					case ClassTableType.UdaMaster:
						this._AddTableOfType(ClassTableType.Master, result);
						this._AddTableOfType(ClassTableType.Subjekt, result);
						break;
					case ClassTableType.Entries:
						this._AddTableOfType(ClassTableType.Master, result);
						this._AddTableOfType(ClassTableType.Subjekt, result);
						break;
					case ClassTableType.UdaEntries:
						this._AddTableOfType(ClassTableType.Entries, result);
						this._AddTableOfType(ClassTableType.Master, result);
						this._AddTableOfType(ClassTableType.Subjekt, result);
						this._AddTableOfType(ClassTableType.UdaMaster, result);
						break;
				}
			}
			return result;
		}
		/// <summary>
		/// Najde a vrátí seznam tabulek, které existují (měly by existovat) a jsou VPRAVO od dané tabulky.
		/// </summary>
		/// <param name="currentTable">Výchozí tabulka</param>
		/// <returns></returns>
		internal List<EntityTableOne> GetRightTables(EntityTableOne currentTable)
		{
			if (currentTable == null) return new List<EntityTableOne>();
			return this.GetRightTables(currentTable.TableType);
		}
		/// <summary>
		/// Najde a vrátí seznam tabulek, které existují (měly by existovat) a jsou VPRAVO od dané tabulky.
		/// </summary>
		/// <param name="currentTableType">Typ výchozí tabulky</param>
		/// <returns></returns>
		internal List<EntityTableOne> GetRightTables(ClassTableType currentTableType)
		{
			bool rootIsEntries = (this.EntityRootTableType == ClassTableType.Entries);      // true, pokud rootem je Entries
			List<EntityTableOne> result = new List<EntityTableOne>();
			if (rootIsEntries)
			{	// Pokud Root tabulkou jsou Entries (entita je navázaná položkovým vztahem):
				switch (currentTableType)
				{
					case ClassTableType.Subjekt:
						this._AddTableOfType(ClassTableType.Master, result);
						this._AddTableOfType(ClassTableType.UdaMaster, result);
						this._AddTableOfType(ClassTableType.UdaEntries, result);
						break;
					case ClassTableType.Master:
						this._AddTableOfType(ClassTableType.Subjekt, result);
						this._AddTableOfType(ClassTableType.UdaMaster, result);
						this._AddTableOfType(ClassTableType.UdaEntries, result);
						break;
					case ClassTableType.UdaMaster:
						this._AddTableOfType(ClassTableType.Entries, result);
						this._AddTableOfType(ClassTableType.UdaEntries, result);
						break;
					case ClassTableType.Entries:
						this._AddTableOfType(ClassTableType.Subjekt, result);
						this._AddTableOfType(ClassTableType.Master, result);
						this._AddTableOfType(ClassTableType.UdaMaster, result);
						this._AddTableOfType(ClassTableType.UdaEntries, result);
						break;
					case ClassTableType.UdaEntries:
						break;
				}
			}
			else
			{	// Root tabulkou je Subjekt / Master:
				switch (currentTableType)
				{
					case ClassTableType.Subjekt:
						this._AddTableOfType(ClassTableType.Master, result);
						this._AddTableOfType(ClassTableType.UdaMaster, result);
						this._AddTableOfType(ClassTableType.Entries, result);
						this._AddTableOfType(ClassTableType.UdaEntries, result);
						break;
					case ClassTableType.Master:
						this._AddTableOfType(ClassTableType.UdaMaster, result);
						this._AddTableOfType(ClassTableType.Entries, result);
						this._AddTableOfType(ClassTableType.UdaEntries, result);
						break;
					case ClassTableType.UdaMaster:
						this._AddTableOfType(ClassTableType.Entries, result);
						this._AddTableOfType(ClassTableType.UdaEntries, result);
						break;
					case ClassTableType.Entries:
						this._AddTableOfType(ClassTableType.UdaEntries, result);
						break;
					case ClassTableType.UdaEntries:
						break;
				}
			}
			return result;
		}
		/// <summary>
		/// Pokud v této entitě existuje daná tabulka (podle typu), přidá ji do výsledku.
		/// </summary>
		/// <param name="tableType">Typ tabulky</param>
		/// <param name="result">Výsledný seznam</param>
		private void _AddTableOfType(ClassTableType tableType, List<EntityTableOne> result)
		{
			EntityTableOne enTable;
			if (this.TableDict.TryGetValue(tableType, out enTable))
				result.Add(enTable);
		}
		/// <summary>
		/// Určí data pro JOIN dané tabulky (parametr 1, joinTable) s napojením na jinou tabulku téže entity (třídy)
		/// (parametr 2, pivotTable).
		/// </summary>
		/// <param name="joinTable">Tabulka, kterou budeme joinovat</param>
		/// <param name="pivotTable">Tabulka téže entity, která už v Query je, a která má být pevným bodem joinu</param>
		/// <param name="joinType">Out typ joinu</param>
		/// <param name="joinClause">Out joinovací výraz</param>
		/// <param name="joinComment">Out komentář</param>
		internal void DetectJoinData(EntityTableOne joinTable, EntityTableOne pivotTable, out QueryEnlistType joinType, out string joinClause, out string joinComment)
		{
			joinType = QueryEnlistType.None;
			joinClause = null;
			joinComment = null;
			string comm = "C=" + this.ClassData.CisloTridy.ToString();
			switch (joinTable.TableType)
			{
				case ClassTableType.Subjekt:
					// Napojuji Subjekt
					switch (pivotTable.TableType)
					{
						case ClassTableType.Master:
							// k tabulce Master:
							joinType = QueryEnlistType.FullJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "M-S, " + comm;
							break;
						case ClassTableType.Entries:
							// k tabulce Entries:
							joinType = QueryEnlistType.FullJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "E-S, " + comm;
							break;
						case ClassTableType.UdaMaster:
							// k tabulce UdaMaster:
							joinType = QueryEnlistType.FullJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "U-S, " + comm;
							break;
						case ClassTableType.UdaEntries:
							// k tabulce UdaEntries:
							joinType = QueryEnlistType.FullJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "P-S, " + comm;
							break;
					}
					break;
				case ClassTableType.Master:
					// Napojuji Master
					switch (pivotTable.TableType)
					{
						case ClassTableType.Subjekt:
							// k tabulce Subjekt:
							joinType = QueryEnlistType.FullJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "S-M, " + comm;
							break;
						case ClassTableType.Entries:
							// k tabulce Entries:
							joinType = QueryEnlistType.FullJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "E-M, " + comm;
							break;
						case ClassTableType.UdaMaster:
							// k tabulce UdaMaster:
							joinType = QueryEnlistType.FullJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "U-M, " + comm;
							break;
						case ClassTableType.UdaEntries:
							// k tabulce UdaEntries:
							joinType = QueryEnlistType.FullJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "P-M, " + comm;
							break;
					}
					break;
				case ClassTableType.Entries:
					// Napojuji Entries
					switch (pivotTable.TableType)
					{
						case ClassTableType.Subjekt:
							// k tabulce Subjekt:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "S-E, " + comm;
							break;
						case ClassTableType.Master:
							// k tabulce Master:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "M-E, " + comm;
							break;
						case ClassTableType.UdaMaster:
							// k tabulce UdaMaster:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "U-E, " + comm;
							break;
						case ClassTableType.UdaEntries:
							// k tabulce UdaEntries:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_objektu = " + pivotTable.Alias + ".cislo_objektu";
							joinComment = "S-E, " + comm;
							break;
					}
					break;
				case ClassTableType.UdaMaster:
					// Napojuji UdaMaster
					switch (pivotTable.TableType)
					{
						case ClassTableType.Subjekt:
							// k tabulce Subjekt:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "S-U, " + comm;
							break;
						case ClassTableType.Master:
							// k tabulce Master:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "M-U, " + comm;
							break;
						case ClassTableType.Entries:
							// k tabulce Entries:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "E-U, " + comm;
							break;
						case ClassTableType.UdaEntries:
							// k tabulce UdaEntries:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "P-U, " + comm;
							break;
					}
					break;
				case ClassTableType.UdaEntries:
					// Napojuji UdaEntries
					switch (pivotTable.TableType)
					{
						case ClassTableType.Subjekt:
							// k tabulce Subjekt:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "S-P, " + comm;
							break;
						case ClassTableType.Master:
							// k tabulce Master:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "M-P, " + comm;
							break;
						case ClassTableType.Entries:
							// k tabulce Entries:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_objektu = " + pivotTable.Alias + ".cislo_objektu";
							joinComment = "E-P, " + comm;
							break;
						case ClassTableType.UdaMaster:
							// k tabulce UdaMaster:
							joinType = QueryEnlistType.LeftJoin;
							joinClause = joinTable.Alias + ".cislo_subjektu = " + pivotTable.Alias + ".cislo_subjektu";
							joinComment = "U-P, " + comm;
							break;
					}
					break;
			}
		}
	}
	/// <summary>
	/// Data o jedné tabulce v rámci entity. Jedna entita = jedna třída, může mít více tabulek 
	/// (podle typu třídy a jejího obsazení: subjekty, master, entries, uda hlavička, uda položky).
	/// </summary>
	internal class EntityTableOne
	{
		public EntityTableOne(EntityTableGroup entity, ClassTableType tableType, string tableName)
		{
			this.Entity = entity;
			this.TableType = tableType;
			this.TableName = tableName;
			this.Alias = null;
		}
		/// <summary>
		/// Vizualizace
		/// </summary>
		/// <returns></returns>
		public override string ToString()
		{
			return "TableType=" + this.TableType.ToString() +
				"; Name=" + this.TableName + (HasAlias ? " alias " + this.Alias : "") +
				"; Prepared=" + (this.IsConnected ? "A" : "N") +
				"; IsFrom =" + (this.IsInFrom ? "A" : "N") +
				"; IsAdd=" + (this.IsAddToQuery ? "A" : "N");
		}
		/// <summary>
		/// Reference na parent entitu
		/// </summary>
		public EntityTableGroup Entity { get; protected set; }
		/// <summary>
		/// Typ tabulky (subjekty, master, entries, uda hlavička, uda položky)
		/// </summary>
		public ClassTableType TableType { get; protected set; }
		/// <summary>
		/// Plný název tabulky včetně prefixu vlastníka
		/// </summary>
		public string TableName { get; protected set; }
		/// <summary>
		/// Název sloupce, který nese klíč této tabulky (cislo_subjektu, cislo_nonsubjektu, cislo_objektu).
		/// </summary>
		public string KeyColumn
		{ get { return this.Entity.ClassData.GetIdentityColumn(this.TableType); } }
		/// <summary>
		/// Je tabulka připravená pro použití v query?
		/// to je tehdy, když a) je do něj přidaná (JOIN/FROM) procesem,
		/// anebo b) se nachází ve frázi FROM, o kterou se tento strom opírá
		/// </summary>
		public bool IsConnected { get { return (this.IsInFrom || this.IsAddToQuery); } }
		/// <summary>
		/// Je tabulka použita z fráze FROM, která je předaná stromu tříd?
		/// </summary>
		public bool IsInFrom { get; set; }
		/// <summary>
		/// Je tabulka explicitně přidaná do Query v rámci stromu tříd?
		/// </summary>
		public bool IsAddToQuery { get; set; }
		/// <summary>
		/// Alias v query
		/// </summary>
		public string Alias { get; set; }
		/// <summary>
		/// Prostor pro uložení aliasu párové strany v JOIN ON frázi.
		/// Jde o alias tabulky, ke které je tato tabulka JOINována, pokud je v Root entitě a hledáme její ukotvení v SELECT FROM.
		/// </summary>
		public string PairAlias { get; set; }
		/// <summary>
		/// Příznak, že tabulka má alias
		/// </summary>
		public bool HasAlias { get { return (Alias != null); } }
		/// <summary>
		/// Metoda vrátí true, pokud tato tabulka může obsahovat sloupec, jehož full db name je předáno.
		/// Metoda neověřuje, zda aktuální tabulka skutečně daný sloupec obsahuje, jen se orientuje podle shody jména tabulky.
		/// </summary>
		/// <param name="fullDbColumnName">Full name sloupce (například lcs.merna_jednotka.reference_subjektu</param>
		/// <returns></returns>
		public bool IsColumnInThisTable(string fullDbColumnName)
		{
			if (fullDbColumnName == null) return false;
			// Pokud daný sloupec začíná textem zdejší tabulky + ".", pak tato tabulka může obsahovat db column:
			return (fullDbColumnName.StartsWith(this.TableName + ".", StringComparison.OrdinalIgnoreCase));
		}
		/// <summary>
		/// Metoda vrátí true, pokud tato tabulka může obsahovat sloupec, jehož full db name je předáno.
		/// Metoda neověřuje, zda aktuální tabulka skutečně daný sloupec obsahuje, jen se orientuje podle shody jména tabulky.
		/// </summary>
		/// <param name="fullDbColumnName">Full name sloupce (například lcs.merna_jednotka.reference_subjektu</param>
		/// <param name="masterTableName">Název master tabulky této entity (třídy).
		/// Pokud se jméno sloupce (fullDbColumnName) odkazuje na tuto master tabulku, ale přitom zdejší tabulka je tabulka lcs.subjekty,
		/// a dotazovaný sloupec je systémový (je umístěn primárně v tabulce subjektů), pak se vrací true pro tuto tabulku (lcs.subjekty),
		/// i když sloupec se odkazuje na master tabulku. Šetří se tím jeden JOIN na Master tabulku.</param>
		/// <returns></returns>
		public bool IsColumnInThisTable(string fullDbColumnName, string masterTableName)
		{
			if (fullDbColumnName == null) return false;

			// Pokud jsem tabulka "lcs.subjekty", a hledám systémový sloupec zadaný do Master tabulky, pak vracím true pro lcs.subjekty:
			if (!String.IsNullOrEmpty(masterTableName)
				&& fullDbColumnName.StartsWith(masterTableName.Trim() + ".", StringComparison.OrdinalIgnoreCase)
				&& IsSystemMasterColumn(fullDbColumnName))
				return true;

			// Pokud daný sloupec začíná textem zdejší tabulky + ".", pak tato tabulka může obsahovat db column:
			return (fullDbColumnName.StartsWith(this.TableName + ".", StringComparison.OrdinalIgnoreCase));
		}
		/// <summary>
		/// Vrací true, pokud je zadán db sloupec, který je systémový (cislo_subjektu, reference_subjektu, nazev_subjektu, cislo_poradace).
		/// Nehledí na název tabulky.
		/// </summary>
		/// <param name="fullDbColumnName"></param>
		/// <returns></returns>
		public static bool IsSystemMasterColumn(string fullDbColumnName)
		{
			string columnName = GetColumnName(fullDbColumnName);
			return (
				columnName == "cislo_subjektu" ||
				columnName == "reference_subjektu" ||
				columnName == "nazev_subjektu" ||
				columnName == "cislo_poradace");
		}
		/// <summary>
		/// Vrátí holé jméno sloupce z daného full dbname (z textu "lcs.merna_jednotka.reference_subjektu" vrátí "reference_subjektu").
		/// Vrací text za poslední tečkou. Pokud tečka není, vrací celý text.
		/// Pokud je na vstupu null, vrací "".
		/// POkud vstupní údaj končí tečkou, vrací prázdný string.
		/// </summary>
		/// <param name="fullDbColumnName"></param>
		/// <returns></returns>
		public static string GetColumnName(string fullDbColumnName)
		{
			if (String.IsNullOrEmpty(fullDbColumnName)) return "";
			int lastDot = fullDbColumnName.LastIndexOf('.');
			return ((lastDot < 0)
				? fullDbColumnName.Trim()
				: (lastDot >= (fullDbColumnName.Length - 1) ? "" : fullDbColumnName.Substring(lastDot + 1).Trim()));
		}
		/// <summary>
		/// Metoda nahradí původní jméno tabulky v daném column fullname jménem aliasu své tabulky.
        /// Používá se pro jeden sloupec, typicky: "lcs.sk_polozka.pocet" =» "p.pocet"
		/// </summary>
		/// <param name="fullDbColumnName"></param>
		/// <returns></returns>
		internal string RenameColumnToAlias(string fullDbColumnName)
		{
			string alias = (this.HasAlias ? this.Alias : this.TableName);
			string[] nameParts = fullDbColumnName.Split('.');
			return alias + "." + nameParts[nameParts.Length - 1];
		}
        /// <summary>
        /// Metoda nahradí původní (základní) jméno své tabulky v daném textu aktuálním aliasem své tabulky.
        /// Používá se pro celý výraz, který může obsahovat odkazy na zdejší tabulku.
        /// Příklad - Mějme výraz: "SELECT pol.pocet FROM lcs.sk_polozka pol WHERE pol.cislo_objektu = lcs.sk_polozka.pohyb_polozka".
        /// Tento výraz je zapojen do selectu do entity "Skladový pohyb", a jeho úkolem je najít počet na (podkladové) položce (alias: pol), 
        /// která je navázaná k číslu "pohyb_polozka" na aktuální (v selectu zařazené) položky (lcs.sk_polozka.pohyb_polozka).
        /// Předpoklad:
        ///  - aktuální tabulka je již zapojena do SELECTU, a má tedy přiděle alias (nebo jej nemusí mít !)
        /// Očekávaný výsledek:
        ///  - data, která se odkazují na zdejší tabulku, se budou odkazovat na její alias (pokud jej tabulka má).
        /// Příklad - výsledek: "SELECT pol.pocet FROM lcs.sk_polozka pol WHERE pol.cislo_objektu = itm.pohyb_polozka".
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        internal string RenameExpressionToAlias(string expression)
        {
            if (!this.HasAlias) return expression;

            string tableName = this.TableName + ".";
            string tableAlias = this.Alias + ".";

            // Tady by asi měl být Warning, pokud vstupující výraz již obsahuje alias, který pro naši tabulku máme my (my = vnější SQL příkaz, v němž se výraz nachází).
            // Ale náš alias může být poměrně jednoduchý, pokud pochází z externě dodaného SQL příkazů (když například do daného SQL příkazu vkládáme filtr).
            // Pak bych mohl mít falešné poplachy...
            // Ideální by bylo? vstupující výraz parsovat, a pracovat až s tím parsovaným, a v něm vyhledávat aktivní texty :-)
            return expression.Replace(tableName, tableAlias);
        }
    }
	/// <summary>
	/// Způsob, jakým je tabulka zařazena do query
	/// </summary>
	internal enum QueryEnlistType
	{
		None = 1,
		From,
		FullJoin,
		LeftJoin
	}
	#endregion
	#endregion
	#region INTERFACE PRO CLASS, KTERÉ DEFINUJÍ STROM TŘÍD GREEN
	/// <summary>
	/// Interface, který předepisuje vlastnosti jednoho prvku v seznamu stromu tříd = jedna třída ve stromu (například FilterEntityCls).
	/// </summary>
	public interface INorisClassTreeEntity
	{
		/// <summary>
		/// Číslo této entity
		/// </summary>
		int EntityNumber { get; }
		/// <summary>
		/// Číslo parent entity
		/// </summary>
		int ParentEntityNumber { get; }
		/// <summary>
		/// Číslo třídy dat v této entitě (nebo 0)
		/// </summary>
		int ClassNumber { get; }
		/// <summary>
		/// Číslo pořadače dat v této entitě (nebo 0)
		/// </summary>
		int FolderNumber { get; }
		/// <summary>
		/// Číslo vztahu, kterým je tato entita navázaná na svého parenta
		/// </summary>
		int RelationNumber { get; }
		/// <summary>
		/// Strana vztahu
		/// </summary>
		RelationSideType RelationSide { get; }
	}
	/// <summary>
	/// Interface, který předepisuje vlastnosti jedné položky, která hledá data ve stromu tříd = jeden řádek filtru (například FilterItemCls).
	/// </summary>
	public interface INorisClassTreeEntry
	{
		/// <summary>
		/// Číslo entity, do které se tento prvek hlásí (její data čte)
		/// </summary>
		int EntityNumber { get; }
		/// <summary>
		/// Plný název db sloupce, který tato položka čte.
        /// Zde může být i výraz (typicky: "(select case when .. then .. else .. end as data from lcs.sk_polozka pol left outer join lcs.sk_hlavicka hl on hl.cislo_subjektu = pol.cislo_subjektu where pol.cislo_subjektu = lcs.sk_polozka.cislo_subjektu and pol.cislo_objektu = lcs.sk_polozka.cislo_objektu)".)
		/// </summary>
		string DataColumnSource { get; }
		/// <summary>
		/// Číslo třídy dat, ze které tato položka čte data
		/// </summary>
		int ClassNumber { get; }
		/// <summary>
		/// Číslo pořadače dat, ze kterého tato položka čte data
		/// </summary>
		int FolderNumber { get; }
	}
	#endregion
}
